History

Distribute platform binaries via optionalDependencies (#488)

* Distribute platform binaries via optionalDependencies The npm wrapper used to download the matching platform binary from GitHub Releases in a postinstall step. That broke `bun i -g executor` two ways: bun blocks postinstalls by default, and the launcher's `#!/usr/bin/env node` shebang fails outright on machines without node. Mirror the pattern opencode and the esbuild/swc family use: - Each per-platform package (executor-linux-x64, executor-darwin-arm64, ...) is published to npm with `os`/`cpu` fields. The wrapper lists them all under `optionalDependencies`, so npm/bun's filtering only fetches the matching one. - The launcher walks parent `node_modules` for the platform package and execs its binary, with a glibc/musl preference chain on Linux. - Postinstall is a fast-path: it hardlinks the resolved binary to `bin/.executor` so subsequent launches skip the walk. It always exits 0 — failures are non-fatal because the launcher does the same walk at runtime, which also covers the bun-blocks-postinstalls case. - The legacy `bin/runtime/` lookup is kept as a fallback so the pkg.pr.new preview wrapper (which downloads per-PR builds from R2) keeps working unchanged. - `publish` now ships every platform package in parallel before the wrapper, so optionalDependencies always resolve to packages that exist on npm. For the no-node-at-all case (e.g. `bun i -g` without node installed), add `scripts/install.sh`: a POSIX installer that downloads the matching release tarball/zip into `~/.executor/bin` and PATH-appends. Same pattern opencode uses for its curl-installer path. Smoke test rewritten to exercise the new flow: build wrapper + current-platform package, set up the install layout `<root>/executor/node_modules/executor-<plat>-<arch>`, run the launcher, boot `executor web`, and verify the bundled UI serves. * install.sh: drop dead bin/ branch, the tarball is flat * Drop postinstall, async signal forwarding, remove secure-exec Codex-style cleanups on top of the optionalDependencies switch: - Drop the postinstall script entirely. The launcher already does the same require.resolve at runtime, so the bin/.executor cache only saves ~1ms per launch and adds surface area. Also removes the bun-blocks- postinstalls failure mode — there's no postinstall to block. - Switch the launcher from spawnSync to async spawn with explicit forwarding of SIGINT/SIGTERM/SIGHUP, so Ctrl-C reaches the binary cleanly during long-running commands like `executor web`. - Detect bun vs npm in the launcher and tell the user the right reinstall command if the platform binary is missing. Remove secure-exec, which is no longer used: - @executor-js/runtime-secure-exec had zero consumers in the codebase (only its own tests imported it). Delete the package, drop its entry from .changeset/config.json. - Remove resolveSecureExecV8 and secureExecBundlePlugin from build.ts; the binary no longer copies a secure-exec-v8 sidecar or runs the esbuild plugin that inlined @secure-exec/nodejs polyfills + bridge code. - Drop the secure-exec mention from main.ts's PATH-augmentation comment and from install.sh's sidecar comment.

Rhys Sullivan committed 6 months ago

Commit c0f618f1 parent 5d586d912 files · +544 −1393Browse files

Showing the changes to scripts (1 of 12 files). Show all

Unified · Split

scripts/install.sh+285−0
@@ -0,0 +1,285 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+APP=executor
4+REPO=RhysSullivan/executor
5+
6+MUTED='\033[0;2m'
7+RED='\033[0;31m'
8+ORANGE='\033[38;5;214m'
9+NC='\033[0m'
10+
11+usage() {
12+ cat <<EOF
13+Executor installer
14+
15+Usage: install.sh [options]
16+
17+Options:
18+ -h, --help Display this help message
19+ -v, --version <version> Install a specific version (e.g. 1.4.12)
20+ -b, --binary <path> Install from a local binary instead of downloading
21+ --no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
22+
23+Examples:
24+ curl -fsSL https://raw.githubusercontent.com/${REPO}/main/scripts/install.sh | bash
25+ curl -fsSL https://raw.githubusercontent.com/${REPO}/main/scripts/install.sh | bash -s -- --version 1.4.12
26+ ./install.sh --binary /path/to/executor
27+EOF
28+}
29+
30+requested_version=${VERSION:-}
31+no_modify_path=false
32+binary_path=""
33+
34+while [[ $# -gt 0 ]]; do
35+ case "$1" in
36+ -h|--help)
37+ usage
38+ exit 0
39+ ;;
40+ -v|--version)
41+ if [[ -n "${2:-}" ]]; then
42+ requested_version="$2"
43+ shift 2
44+ else
45+ echo -e "${RED}Error: --version requires a version argument${NC}" >&2
46+ exit 1
47+ fi
48+ ;;
49+ -b|--binary)
50+ if [[ -n "${2:-}" ]]; then
51+ binary_path="$2"
52+ shift 2
53+ else
54+ echo -e "${RED}Error: --binary requires a path argument${NC}" >&2
55+ exit 1
56+ fi
57+ ;;
58+ --no-modify-path)
59+ no_modify_path=true
60+ shift
61+ ;;
62+ *)
63+ echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
64+ shift
65+ ;;
66+ esac
67+done
68+
69+INSTALL_DIR="${EXECUTOR_INSTALL_DIR:-$HOME/.executor/bin}"
70+mkdir -p "$INSTALL_DIR"
71+
72+print_message() {
73+ local level=$1 message=$2 color=""
74+ case "$level" in
75+ info) color="${NC}" ;;
76+ warning) color="${ORANGE}" ;;
77+ error) color="${RED}" ;;
78+ esac
79+ echo -e "${color}${message}${NC}"
80+}
81+
82+if [[ -n "$binary_path" ]]; then
83+ if [[ ! -f "$binary_path" ]]; then
84+ print_message error "Error: binary not found at $binary_path"
85+ exit 1
86+ fi
87+ specific_version="local"
88+else
89+ raw_os=$(uname -s)
90+ case "$raw_os" in
91+ Darwin*) os="darwin" ;;
92+ Linux*) os="linux" ;;
93+ MINGW*|MSYS*|CYGWIN*) os="windows" ;;
94+ *)
95+ print_message error "Unsupported OS: $raw_os"
96+ exit 1
97+ ;;
98+ esac
99+
100+ arch=$(uname -m)
101+ case "$arch" in
102+ aarch64|arm64) arch="arm64" ;;
103+ x86_64|amd64) arch="x64" ;;
104+ *)
105+ print_message error "Unsupported architecture: $arch"
106+ exit 1
107+ ;;
108+ esac
109+
110+ # Apple Silicon under Rosetta reports x64 — install the native arm64 build.
111+ if [[ "$os" == "darwin" && "$arch" == "x64" ]]; then
112+ if [[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" == "1" ]]; then
113+ arch="arm64"
114+ fi
115+ fi
116+
117+ is_musl=false
118+ if [[ "$os" == "linux" ]]; then
119+ if [[ -f /etc/alpine-release ]]; then
120+ is_musl=true
121+ elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
122+ is_musl=true
123+ fi
124+ fi
125+
126+ target="${os}-${arch}"
127+ if [[ "$is_musl" == "true" ]]; then
128+ target="${target}-musl"
129+ fi
130+
131+ archive_ext=".zip"
132+ if [[ "$os" == "linux" ]]; then
133+ archive_ext=".tar.gz"
134+ fi
135+
136+ filename="${APP}-${target}${archive_ext}"
137+
138+ if [[ "$os" == "linux" ]]; then
139+ if ! command -v tar >/dev/null 2>&1; then
140+ print_message error "Error: 'tar' is required but not installed."
141+ exit 1
142+ fi
143+ else
144+ if ! command -v unzip >/dev/null 2>&1; then
145+ print_message error "Error: 'unzip' is required but not installed."
146+ exit 1
147+ fi
148+ fi
149+
150+ if [[ -z "$requested_version" ]]; then
151+ url="https://github.com/${REPO}/releases/latest/download/${filename}"
152+ specific_version=$(
153+ curl -s "https://api.github.com/repos/${REPO}/releases/latest" \
154+ | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p'
155+ )
156+ if [[ -z "$specific_version" ]]; then
157+ print_message error "Failed to fetch latest version metadata"
158+ exit 1
159+ fi
160+ else
161+ requested_version="${requested_version#v}"
162+ url="https://github.com/${REPO}/releases/download/v${requested_version}/${filename}"
163+ specific_version="$requested_version"
164+
165+ http_status=$(curl -sI -o /dev/null -w "%{http_code}" \
166+ "https://github.com/${REPO}/releases/tag/v${requested_version}")
167+ if [[ "$http_status" == "404" ]]; then
168+ print_message error "Error: release v${requested_version} not found"
169+ print_message info "${MUTED}Available releases: https://github.com/${REPO}/releases${NC}"
170+ exit 1
171+ fi
172+ fi
173+fi
174+
175+check_existing_version() {
176+ if command -v executor >/dev/null 2>&1; then
177+ local installed
178+ installed=$(executor --version 2>/dev/null || echo "")
179+ if [[ "$installed" == "$specific_version" ]]; then
180+ print_message info "${MUTED}Version ${NC}${specific_version}${MUTED} already installed${NC}"
181+ exit 0
182+ fi
183+ print_message info "${MUTED}Replacing installed version ${NC}${installed}"
184+ fi
185+}
186+
187+download_and_install() {
188+ print_message info "\n${MUTED}Installing ${NC}${APP} ${MUTED}version: ${NC}${specific_version}"
189+ local tmp_dir="${TMPDIR:-/tmp}/${APP}_install_$$"
190+ mkdir -p "$tmp_dir"
191+
192+ curl -# -L -o "${tmp_dir}/${filename}" "$url"
193+
194+ if [[ "$os" == "linux" ]]; then
195+ tar -xzf "${tmp_dir}/${filename}" -C "$tmp_dir"
196+ else
197+ unzip -q "${tmp_dir}/${filename}" -d "$tmp_dir"
198+ fi
199+
200+ # The archive is flat — the binary plus sidecars (emscripten-module.wasm,
201+ # keyring.node) sit at the root. Copy them all into INSTALL_DIR so the
202+ # binary's relative-path lookups still resolve.
203+ rm -f "${tmp_dir}/${filename}"
204+ cp -R "${tmp_dir}/." "${INSTALL_DIR}/"
205+
206+ chmod 755 "${INSTALL_DIR}/${APP}"
207+ rm -rf "$tmp_dir"
208+}
209+
210+install_from_binary() {
211+ print_message info "\n${MUTED}Installing ${NC}${APP} ${MUTED}from: ${NC}${binary_path}"
212+ cp "$binary_path" "${INSTALL_DIR}/${APP}"
213+ chmod 755 "${INSTALL_DIR}/${APP}"
214+}
215+
216+if [[ -n "$binary_path" ]]; then
217+ install_from_binary
218+else
219+ check_existing_version
220+ download_and_install
221+fi
222+
223+add_to_path() {
224+ local config_file=$1 command=$2
225+ if grep -Fxq "$command" "$config_file"; then
226+ print_message info "${MUTED}Already in ${NC}${config_file}"
227+ elif [[ -w "$config_file" ]]; then
228+ echo -e "\n# executor" >> "$config_file"
229+ echo "$command" >> "$config_file"
230+ print_message info "${MUTED}Added ${NC}${APP} ${MUTED}to \$PATH in ${NC}${config_file}"
231+ else
232+ print_message warning "Manually add to ${config_file}:"
233+ print_message info " $command"
234+ fi
235+}
236+
237+XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
238+current_shell=$(basename "${SHELL:-bash}")
239+
240+case "$current_shell" in
241+ fish)
242+ config_files="$HOME/.config/fish/config.fish"
243+ ;;
244+ zsh)
245+ config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
246+ ;;
247+ bash)
248+ config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
249+ ;;
250+ *)
251+ config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
252+ ;;
253+esac
254+
255+if [[ "$no_modify_path" != "true" ]]; then
256+ config_file=""
257+ for file in $config_files; do
258+ if [[ -f "$file" ]]; then
259+ config_file=$file
260+ break
261+ fi
262+ done
263+
264+ if [[ -z "$config_file" ]]; then
265+ print_message warning "No config file found for ${current_shell}. Add manually:"
266+ print_message info " export PATH=${INSTALL_DIR}:\$PATH"
267+ elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
268+ case "$current_shell" in
269+ fish) add_to_path "$config_file" "fish_add_path $INSTALL_DIR" ;;
270+ *) add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH" ;;
271+ esac
272+ fi
273+fi
274+
275+if [[ -n "${GITHUB_ACTIONS-}" && "${GITHUB_ACTIONS}" == "true" ]]; then
276+ echo "$INSTALL_DIR" >> "$GITHUB_PATH"
277+ print_message info "${MUTED}Added ${NC}${INSTALL_DIR}${MUTED} to \$GITHUB_PATH${NC}"
278+fi
279+
280+print_message info ""
281+print_message info "${MUTED}Installed ${NC}${APP} ${MUTED}to ${NC}${INSTALL_DIR}/${APP}"
282+print_message info ""
283+print_message info "${MUTED}Get started:${NC}"
284+print_message info " ${APP} web"
285+print_message info ""