1
0

build-bundle.sh 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #!/usr/bin/env bash
  2. #
  3. # Build a self-contained CodeGraph bundle: an official Node runtime + the
  4. # compiled app + its production deps, so CodeGraph runs with NO system Node and
  5. # NO native build — node:sqlite is built into the bundled Node. One archive per
  6. # platform.
  7. #
  8. # Because dropping better-sqlite3 left zero native addons, the recipe is pure
  9. # file-packaging (download the target's Node, copy the app, archive) — so any
  10. # platform's bundle can be built on any OS. No cross-compile, no native runners.
  11. #
  12. # Usage:
  13. # scripts/build-bundle.sh <target> [node-version]
  14. # target: darwin-arm64 | darwin-x64 | linux-x64 | linux-arm64
  15. # | win32-x64 | win32-arm64
  16. # node-version: e.g. v24.16.0 (default below; pin for reproducible builds)
  17. #
  18. # Output:
  19. # unix: release/codegraph-<target>.tar.gz (launcher: bin/codegraph)
  20. # windows: release/codegraph-<target>.zip (launcher: bin/codegraph.cmd)
  21. set -euo pipefail
  22. TARGET="${1:?usage: build-bundle.sh <target> [node-version]}"
  23. NODE_VERSION="${2:-v24.16.0}"
  24. ROOT="$(cd "$(dirname "$0")/.." && pwd)"
  25. OUT="$ROOT/release"
  26. WORK="$(mktemp -d)"
  27. trap 'rm -rf "$WORK"' EXIT
  28. ARCH="${TARGET##*-}" # x64 | arm64
  29. OSFAM="${TARGET%-*}" # darwin | linux | win32
  30. echo "[bundle] target=${TARGET} node=${NODE_VERSION}"
  31. # 1. Download + extract the official Node runtime for the target platform.
  32. if [ "$OSFAM" = "win32" ]; then
  33. NODE_DIST="node-${NODE_VERSION}-win-${ARCH}"
  34. NODE_URL="https://nodejs.org/dist/${NODE_VERSION}/${NODE_DIST}.zip"
  35. echo "[bundle] downloading ${NODE_URL}"
  36. curl -fsSL "$NODE_URL" -o "$WORK/node.zip"
  37. if command -v unzip >/dev/null 2>&1; then
  38. unzip -q "$WORK/node.zip" -d "$WORK"
  39. else
  40. tar -xf "$WORK/node.zip" -C "$WORK" # bsdtar can read zip
  41. fi
  42. NODE_BIN="$WORK/${NODE_DIST}/node.exe"
  43. else
  44. NODE_DIST="node-${NODE_VERSION}-${TARGET}"
  45. NODE_URL="https://nodejs.org/dist/${NODE_VERSION}/${NODE_DIST}.tar.gz"
  46. echo "[bundle] downloading ${NODE_URL}"
  47. curl -fsSL "$NODE_URL" -o "$WORK/node.tar.gz"
  48. tar -xzf "$WORK/node.tar.gz" -C "$WORK"
  49. NODE_BIN="$WORK/${NODE_DIST}/bin/node"
  50. fi
  51. [ -f "$NODE_BIN" ] || { echo "[bundle] error: node binary not found ($NODE_BIN)" >&2; exit 1; }
  52. # 2. Build the app (compiled JS + copied wasm/schema assets).
  53. echo "[bundle] building app"
  54. ( cd "$ROOT" && npm run build >/dev/null )
  55. # 3. Stage: app + production-only deps (pure JS/wasm → portable across platforms).
  56. STAGE="$WORK/codegraph-${TARGET}"
  57. mkdir -p "$STAGE/lib" "$STAGE/bin"
  58. cp -R "$ROOT/dist" "$STAGE/lib/dist"
  59. # The browser viewer rides along inside dist/viewer (built by `npm run build`
  60. # above). Fail here rather than shipping a bundle whose `codegraph ui` serves
  61. # a 404 — the copy is verified, not assumed.
  62. node "$ROOT/scripts/check-ui-build.mjs" --root "$STAGE/lib"
  63. cp "$ROOT/package.json" "$ROOT/package-lock.json" "$STAGE/lib/"
  64. echo "[bundle] installing production dependencies"
  65. # The staged package.json declares the `ui` workspace but the bundle carries
  66. # no ui/ source — only its build output. That is fine: ui/ has dev
  67. # dependencies only, so --omit=dev skips the workspace outright and no link
  68. # is created. (If a future npm starts erroring on the absent folder, stage a
  69. # stub ui/package.json before this line rather than editing the lock.)
  70. ( cd "$STAGE/lib" && npm ci --omit=dev --ignore-scripts >/dev/null 2>&1 )
  71. rm -f "$STAGE/lib/package-lock.json"
  72. # 3b. Native extraction kernel (optional). Included when a prebuilt .node for
  73. # the target exists — release/kernel/<target>/codegraph-kernel.node (the
  74. # release workflow's prebuild artifacts) or the locally staged
  75. # codegraph-kernel/prebuilds/<target>/ (scripts/build-kernel.sh). Absent →
  76. # the bundle simply runs the wasm extraction path; the kernel is a
  77. # per-language speedup, never a requirement (see
  78. # docs/design/rust-kernel-migration-plan.md).
  79. KERNEL_NODE=""
  80. for candidate in "$ROOT/release/kernel/${TARGET}/codegraph-kernel.node" \
  81. "$ROOT/codegraph-kernel/prebuilds/${TARGET}/codegraph-kernel.node"; do
  82. if [ -f "$candidate" ]; then KERNEL_NODE="$candidate"; break; fi
  83. done
  84. if [ -n "$KERNEL_NODE" ]; then
  85. mkdir -p "$STAGE/lib/kernel"
  86. cp "$KERNEL_NODE" "$STAGE/lib/kernel/codegraph-kernel.node"
  87. echo "[bundle] native kernel included ($KERNEL_NODE)"
  88. else
  89. echo "[bundle] no native kernel for ${TARGET} — bundle uses the wasm extraction path"
  90. fi
  91. # 4. Vendored Node + launcher (the launcher uses the bundled Node by relative
  92. # path, so no system Node is ever needed).
  93. #
  94. # `--liftoff-only`: keep tree-sitter's large WASM grammars on V8's Liftoff
  95. # baseline compiler so they never reach the turboshaft optimizing tier, whose
  96. # per-compilation Zone arena OOMs the whole process (`Fatal process out of
  97. # memory: Zone`) on Node >= 22 — even with tens of GB free. The flag is read at
  98. # V8 engine init so it must be on node's command line; the parse worker inherits
  99. # it. See issues #293/#298 and src/extraction/wasm-runtime-flags.ts. (The CLI
  100. # also self-relaunches with this flag when launched without it, so non-bundled
  101. # runs are covered too; passing it here avoids that extra spawn.)
  102. if [ "$OSFAM" = "win32" ]; then
  103. cp "$NODE_BIN" "$STAGE/node.exe"
  104. printf '@"%%~dp0..\\node.exe" --liftoff-only --disable-warning=ExperimentalWarning "%%~dp0..\\lib\\dist\\bin\\codegraph.js" %%*\r\n' \
  105. > "$STAGE/bin/codegraph.cmd"
  106. else
  107. cp "$NODE_BIN" "$STAGE/node"
  108. cat > "$STAGE/bin/codegraph" <<'LAUNCH'
  109. #!/bin/sh
  110. # Resolve symlinks (e.g. the ~/.local/bin/codegraph link install.sh creates) so
  111. # we find the real bundle dir, not the symlink's location.
  112. SELF="$0"
  113. while [ -L "$SELF" ]; do
  114. target="$(readlink "$SELF")"
  115. case "$target" in
  116. /*) SELF="$target" ;;
  117. *) SELF="$(dirname "$SELF")/$target" ;;
  118. esac
  119. done
  120. DIR="$(cd "$(dirname "$SELF")/.." && pwd)"
  121. # Thread the MCP host's pid to the server's orphan watchdog (issue #1185).
  122. # $PPID is our parent — the host itself when it launched this script directly;
  123. # an already-threaded value (the npm shim sets the true host pid) wins.
  124. CODEGRAPH_HOST_PPID="${CODEGRAPH_HOST_PPID:-$PPID}"
  125. export CODEGRAPH_HOST_PPID
  126. # --liftoff-only: avoid the V8 turboshaft WASM Zone OOM (issues #293/#298).
  127. # --disable-warning=ExperimentalWarning: mute node:sqlite's per-thread
  128. # "experimental feature" warning that otherwise interleaves with the progress UI.
  129. exec "$DIR/node" --liftoff-only --disable-warning=ExperimentalWarning "$DIR/lib/dist/bin/codegraph.js" "$@"
  130. LAUNCH
  131. chmod +x "$STAGE/bin/codegraph"
  132. fi
  133. # 5. Archive (.zip for Windows, .tar.gz otherwise).
  134. mkdir -p "$OUT"
  135. if [ "$OSFAM" = "win32" ]; then
  136. ARCHIVE="$OUT/codegraph-${TARGET}.zip"
  137. rm -f "$ARCHIVE"
  138. ( cd "$WORK" && zip -rqX "$ARCHIVE" "codegraph-${TARGET}" )
  139. else
  140. ARCHIVE="$OUT/codegraph-${TARGET}.tar.gz"
  141. # --no-xattrs: don't embed macOS xattrs that make GNU tar warn on Linux.
  142. tar --no-xattrs -czf "$ARCHIVE" -C "$WORK" "codegraph-${TARGET}"
  143. fi
  144. echo "[bundle] wrote ${ARCHIVE} ($(du -h "$ARCHIVE" | cut -f1))"