build_vscode.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. #!/bin/bash
  2. # Opencode VSCode Extension Build Script
  3. # This script handles the complete build process for the Opencode VSCode extension.
  4. # Supports building two variants:
  5. # - Standard: bundles opencode binaries (default)
  6. # - GUI-only: no binaries, uses system opencode, embeds webgui-dist
  7. #
  8. # By default both variants are built. Use --gui-only or --standard-only to
  9. # build a single variant.
  10. set -e
  11. # Colors for output
  12. RED='\033[0;31m'
  13. GREEN='\033[0;32m'
  14. YELLOW='\033[1;33m'
  15. BLUE='\033[0;34m'
  16. NC='\033[0m' # No Color
  17. # Script directory references
  18. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  19. ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
  20. PLUGIN_DIR="$ROOT_DIR/hosts/vscode-plugin"
  21. WEBGUI_DIR="$ROOT_DIR/packages/opencode/webgui"
  22. WEBGUI_DIST="$ROOT_DIR/packages/opencode/webgui-dist"
  23. echo -e "${BLUE}Opencode VSCode Extension Build Script${NC}"
  24. echo "Plugin directory: $PLUGIN_DIR"
  25. echo "Root directory: $ROOT_DIR"
  26. # --- Package manager helpers ---
  27. PNPM_AVAILABLE=false
  28. if command -v pnpm >/dev/null 2>&1; then
  29. PNPM_AVAILABLE=true
  30. fi
  31. run_install() {
  32. if $PNPM_AVAILABLE; then
  33. pnpm install --frozen-lockfile
  34. else
  35. npm ci || npm install
  36. fi
  37. }
  38. run_script() {
  39. local script="$1"
  40. if $PNPM_AVAILABLE; then
  41. pnpm run "$script"
  42. else
  43. npm run "$script"
  44. fi
  45. }
  46. print_status() {
  47. echo -e "${GREEN}[INFO]${NC} $1"
  48. }
  49. print_warning() {
  50. echo -e "${YELLOW}[WARN]${NC} $1"
  51. }
  52. print_error() {
  53. echo -e "${RED}[ERROR]${NC} $1"
  54. }
  55. if [ ! -f "$PLUGIN_DIR/package.json" ]; then
  56. print_error "package.json not found. Please run this script from the repository root."
  57. exit 1
  58. fi
  59. BUILD_TYPE="development"
  60. SKIP_BINARIES=false
  61. SKIP_TESTS=false
  62. PACKAGE_ONLY=false
  63. BUILD_STANDARD=true
  64. BUILD_GUI_ONLY=true
  65. while [[ $# -gt 0 ]]; do
  66. case $1 in
  67. --production)
  68. BUILD_TYPE="production"
  69. shift
  70. ;;
  71. --skip-binaries)
  72. SKIP_BINARIES=true
  73. shift
  74. ;;
  75. --skip-tests)
  76. SKIP_TESTS=true
  77. shift
  78. ;;
  79. --package-only)
  80. PACKAGE_ONLY=true
  81. shift
  82. ;;
  83. --gui-only)
  84. BUILD_STANDARD=false
  85. BUILD_GUI_ONLY=true
  86. shift
  87. ;;
  88. --standard-only)
  89. BUILD_STANDARD=true
  90. BUILD_GUI_ONLY=false
  91. shift
  92. ;;
  93. --help)
  94. echo "Usage: $0 [OPTIONS]"
  95. echo "Options:"
  96. echo " --production Build for production (default: development)"
  97. echo " --skip-binaries Skip building backend binaries"
  98. echo " --skip-tests Skip running tests"
  99. echo " --package-only Only create the .vsix package (skip compilation)"
  100. echo " --gui-only Build only the gui-only variant (no binaries)"
  101. echo " --standard-only Build only the standard variant (with binaries)"
  102. echo " --help Show this help message"
  103. exit 0
  104. ;;
  105. *)
  106. print_error "Unknown option: $1"
  107. exit 1
  108. ;;
  109. esac
  110. done
  111. print_status "Building VSCode extension in $BUILD_TYPE mode"
  112. [ "$BUILD_STANDARD" = true ] && print_status " Variant: standard (with binaries)"
  113. [ "$BUILD_GUI_ONLY" = true ] && print_status " Variant: gui-only (system opencode)"
  114. cd "$PLUGIN_DIR"
  115. # ─── Shared preparation (compile once, package per-variant) ───────────────
  116. if [ "$PACKAGE_ONLY" = false ]; then
  117. print_status "Cleaning previous build artifacts..."
  118. set +e
  119. if [ ! -d node_modules ]; then
  120. print_warning "Dependencies not installed; skipping script clean and removing artifacts manually."
  121. rm -rf out
  122. rm -f ./*.vsix
  123. fi
  124. if [ -d node_modules ]; then
  125. run_script clean
  126. if [[ $? -ne 0 ]]; then
  127. print_warning "Clean command failed, applying fallback removal..."
  128. rm -rf out
  129. rm -f ./*.vsix
  130. fi
  131. fi
  132. set -e
  133. fi
  134. if [ "$PACKAGE_ONLY" = false ]; then
  135. print_status "Installing dependencies..."
  136. if ! command -v node >/dev/null 2>&1; then
  137. print_error "Node.js is required but not found in PATH. Please install Node.js."
  138. exit 1
  139. fi
  140. run_install
  141. fi
  142. if [ "$SKIP_BINARIES" = false ] && [ "$PACKAGE_ONLY" = false ] && [ "$BUILD_STANDARD" = true ]; then
  143. print_status "Building backend binaries..."
  144. "$SCRIPT_DIR/build_opencode.sh"
  145. fi
  146. if [ "$PACKAGE_ONLY" = false ]; then
  147. print_status "Compiling TypeScript..."
  148. if [ "$BUILD_TYPE" = "production" ]; then
  149. run_script compile:production
  150. else
  151. run_script compile
  152. fi
  153. fi
  154. if [ "$PACKAGE_ONLY" = false ]; then
  155. print_status "Running linter..."
  156. set +e
  157. run_script lint
  158. if [[ $? -ne 0 ]]; then
  159. print_warning "Linting failed, continuing with build..."
  160. fi
  161. set -e
  162. fi
  163. if [ "$SKIP_TESTS" = false ] && [ "$PACKAGE_ONLY" = false ]; then
  164. print_status "Running tests..."
  165. set +e
  166. run_script test
  167. if [[ $? -ne 0 ]]; then
  168. print_warning "Tests failed, continuing with build..."
  169. fi
  170. set -e
  171. fi
  172. # ─── Resolve vsce command ────────────────────────────────────────────────
  173. VSCE_CMD="vsce"
  174. if ! command -v vsce >/dev/null 2>&1; then
  175. if command -v npx >/dev/null 2>&1; then
  176. VSCE_CMD="npx -y @vscode/vsce"
  177. else
  178. print_warning "vsce not found and npx unavailable; attempting global install via npm"
  179. npm install -g @vscode/vsce
  180. fi
  181. fi
  182. TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
  183. # ─── Build helper: package a single variant ──────────────────────────────
  184. build_variant() {
  185. local variant="$1" # "standard" or "gui-only"
  186. cd "$PLUGIN_DIR"
  187. if [ "$variant" = "standard" ]; then
  188. print_status "=== Packaging STANDARD variant ==="
  189. # Check for required binaries
  190. BINARY_PATHS=(
  191. "resources/bin/windows/amd64/opencode.exe"
  192. "resources/bin/macos/amd64/opencode"
  193. "resources/bin/macos/arm64/opencode"
  194. "resources/bin/linux/amd64/opencode"
  195. "resources/bin/linux/arm64/opencode"
  196. )
  197. MISSING_BINARIES=false
  198. for binary_path in "${BINARY_PATHS[@]}"; do
  199. if [ ! -f "$binary_path" ]; then
  200. print_warning "Missing binary: $binary_path"
  201. MISSING_BINARIES=true
  202. fi
  203. done
  204. if [ "$MISSING_BINARIES" = true ]; then
  205. print_warning "Some binaries are missing. The extension may not work on all platforms."
  206. print_warning "Run '$SCRIPT_DIR/build_opencode.sh' from the root directory to build all binaries."
  207. fi
  208. # Package with original package.json and .vscodeignore
  209. if [ "$BUILD_TYPE" = "production" ]; then
  210. eval "$VSCE_CMD package --no-dependencies --out 'opencode-vscode-${TIMESTAMP}.vsix'"
  211. else
  212. eval "$VSCE_CMD package --pre-release --no-dependencies --out 'opencode-vscode-dev-${TIMESTAMP}.vsix'"
  213. fi
  214. elif [ "$variant" = "gui-only" ]; then
  215. print_status "=== Packaging GUI-ONLY variant ==="
  216. # Always rebuild webgui to pick up source changes
  217. # The monorepo uses bun workspaces – deps are already installed at root level
  218. print_status "Building webgui..."
  219. if command -v bun >/dev/null 2>&1; then
  220. (cd "$WEBGUI_DIR" && bun run build)
  221. else
  222. (cd "$WEBGUI_DIR" && npm run build)
  223. fi
  224. if [ ! -d "$WEBGUI_DIST" ]; then
  225. print_error "webgui-dist not found at $WEBGUI_DIST after build"
  226. return 1
  227. fi
  228. # Copy webgui-dist into plugin resources for embedding
  229. print_status "Embedding webgui-dist into plugin resources..."
  230. rm -rf "$PLUGIN_DIR/resources/webgui-app"
  231. cp -r "$WEBGUI_DIST" "$PLUGIN_DIR/resources/webgui-app"
  232. # Move binaries completely outside the plugin tree so vsce cannot bundle them
  233. BIN_STASH="$(mktemp -d)"
  234. if [ -d "$PLUGIN_DIR/resources/bin" ]; then
  235. mv "$PLUGIN_DIR/resources/bin" "$BIN_STASH/bin"
  236. fi
  237. # Temporarily swap package.json with gui-only overrides and .vscodeignore
  238. cp "$PLUGIN_DIR/package.json" "$PLUGIN_DIR/package.json.bak"
  239. cp "$PLUGIN_DIR/.vscodeignore" "$PLUGIN_DIR/.vscodeignore.bak"
  240. # Ensure originals are restored even on failure
  241. gui_only_cleanup() {
  242. cd "$PLUGIN_DIR"
  243. [ -f package.json.bak ] && mv package.json.bak package.json
  244. [ -f .vscodeignore.bak ] && mv .vscodeignore.bak .vscodeignore
  245. [ -d "$BIN_STASH/bin" ] && mv "$BIN_STASH/bin" resources/bin
  246. rm -rf "$BIN_STASH"
  247. rm -rf resources/webgui-app
  248. }
  249. trap gui_only_cleanup EXIT
  250. # Deep-merge gui-only overrides into package.json
  251. node -e "
  252. const fs = require('fs');
  253. function deep(target, src) {
  254. for (const key of Object.keys(src)) {
  255. const s = src[key];
  256. const t = target[key];
  257. if (Array.isArray(s) && Array.isArray(t)) {
  258. for (let i = 0; i < s.length; i++) {
  259. if (i < t.length && typeof s[i] === 'object' && typeof t[i] === 'object') {
  260. deep(t[i], s[i]);
  261. } else {
  262. t[i] = s[i];
  263. }
  264. }
  265. } else if (s && typeof s === 'object' && !Array.isArray(s) && t && typeof t === 'object' && !Array.isArray(t)) {
  266. deep(t, s);
  267. } else {
  268. target[key] = s;
  269. }
  270. }
  271. return target;
  272. }
  273. const base = JSON.parse(fs.readFileSync('package.json', 'utf8'));
  274. const overrides = JSON.parse(fs.readFileSync('package.gui-only.json', 'utf8'));
  275. fs.writeFileSync('package.json', JSON.stringify(deep(base, overrides), null, 2) + '\n');
  276. "
  277. # Swap .vscodeignore
  278. cp "$PLUGIN_DIR/.vscodeignore.gui-only" "$PLUGIN_DIR/.vscodeignore"
  279. # Package gui-only variant
  280. if [ "$BUILD_TYPE" = "production" ]; then
  281. eval "$VSCE_CMD package --no-dependencies --out 'opencode-vscode-gui-only-${TIMESTAMP}.vsix'"
  282. else
  283. eval "$VSCE_CMD package --pre-release --no-dependencies --out 'opencode-vscode-gui-only-dev-${TIMESTAMP}.vsix'"
  284. fi
  285. # Restore originals (also handled by trap on failure)
  286. gui_only_cleanup
  287. trap - EXIT
  288. print_status "GUI-only variant packaged successfully"
  289. fi
  290. }
  291. # ─── Build requested variants ────────────────────────────────────────────
  292. if [ "$BUILD_STANDARD" = true ]; then
  293. build_variant "standard"
  294. fi
  295. if [ "$BUILD_GUI_ONLY" = true ]; then
  296. build_variant "gui-only"
  297. fi
  298. print_status "Build completed successfully!"
  299. print_status "Extension packages created in: $PLUGIN_DIR"
  300. shopt -s nullglob
  301. VSIX_FILES=( "$PLUGIN_DIR"/*.vsix )
  302. shopt -u nullglob
  303. if ((${#VSIX_FILES[@]} > 0)); then
  304. echo "Packages created:"
  305. for vsix in "${VSIX_FILES[@]}"; do
  306. echo " $(basename "$vsix")"
  307. done
  308. fi