* 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.
Showing the changes to scripts (1 of 12 files). Show all
Unified · Split
| @@ -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 "" | ||