diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ad7bd6279..633f0f06b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Installer: install Node.js through `apk` on Alpine Linux instead of falling through to the NodeSource package-manager path. - Installer: detect musl Linux shells such as Alpine as Linux instead of rejecting them before npm install. - Control UI: split large build-time runtime dependencies into stable chunks so Linux/Docker install and package builds stay below the app chunk warning threshold. - Scripts: run the optional Discord native opus installer through the shared pnpm launcher and Windows CI coverage so native Windows installs avoid shell-mode package-manager shims. diff --git a/scripts/install.sh b/scripts/install.sh index 4979e7692dd..b7fbbe47155 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1621,6 +1621,21 @@ check_node() { fi } +finish_linux_node_install() { + activate_supported_node_on_path || true + if ! node_is_at_least_required; then + local active_path active_version + active_path="$(command -v node 2>/dev/null || echo "not found")" + active_version="$(node -v 2>/dev/null || echo "missing")" + ui_error "Installed Node.js must be v${NODE_MIN_VERSION}+ but this shell is using ${active_version} (${active_path})" + echo "Upgrade the system Node.js package or install Node.js ${NODE_DEFAULT_MAJOR} manually, then rerun the installer." + exit 1 + fi + + ui_success "Node.js v$(node -v | cut -d'v' -f2) installed" + print_active_node_paths || true +} + # Install Node.js install_node() { if [[ "$OS" == "macos" ]]; then @@ -1653,9 +1668,18 @@ install_node() { else run_quiet_step "Installing Node.js" sudo pacman -Sy --noconfirm nodejs npm fi - promote_supported_node_binary || true - ui_success "Node.js v${NODE_DEFAULT_MAJOR} installed" - print_active_node_paths || true + finish_linux_node_install + return 0 + fi + + if command -v apk &> /dev/null; then + ui_info "Installing Node.js via apk (Alpine Linux detected)" + if is_root; then + run_quiet_step "Installing Node.js" apk add --no-cache nodejs npm + else + run_quiet_step "Installing Node.js" sudo apk add --no-cache nodejs npm + fi + finish_linux_node_install return 0 fi @@ -1699,9 +1723,7 @@ install_node() { exit 1 fi - ui_success "Node.js v${NODE_DEFAULT_MAJOR} installed" - activate_supported_node_on_path || true - print_active_node_paths || true + finish_linux_node_install fi } diff --git a/test/scripts/install-sh.test.ts b/test/scripts/install-sh.test.ts index ab7096b23c4..c70417ae182 100644 --- a/test/scripts/install-sh.test.ts +++ b/test/scripts/install-sh.test.ts @@ -39,6 +39,46 @@ describe("install.sh", () => { expect(script).not.toContain('[[ "$OSTYPE" == "linux-gnu"* ]]'); }); + it("installs Node.js with apk on Alpine before falling back to NodeSource", () => { + expect(script).toContain("finish_linux_node_install()"); + expect(script).toContain('ui_info "Installing Node.js via apk (Alpine Linux detected)"'); + expect(script).toContain( + 'run_quiet_step "Installing Node.js" apk add --no-cache nodejs npm', + ); + expect(script).toContain( + 'run_quiet_step "Installing Node.js" sudo apk add --no-cache nodejs npm', + ); + expect(script).toContain('if ! node_is_at_least_required; then'); + + const apkIndex = script.indexOf('if command -v apk &> /dev/null; then'); + const nodeSourceIndex = script.indexOf('ui_info "Installing Node.js via NodeSource"'); + expect(apkIndex).toBeGreaterThan(-1); + expect(nodeSourceIndex).toBeGreaterThan(apkIndex); + }); + + it("uses the apk Node.js installer path on Alpine", () => { + const result = runInstallShell(` + set -euo pipefail + source "${SCRIPT_PATH}" + OS=linux + require_sudo() { :; } + install_build_tools_linux() { return 0; } + is_root() { return 0; } + ui_info() { printf 'info:%s\\n' "$*"; } + ui_success() { printf 'success:%s\\n' "$*"; } + run_quiet_step() { printf 'step:%s|%s\\n' "$1" "\${*:2}"; } + apk() { :; } + finish_linux_node_install() { printf 'finish-linux-node\\n'; } + install_node + `); + + expect(result.status).toBe(0); + expect(result.stdout).toContain("info:Installing Node.js via apk (Alpine Linux detected)"); + expect(result.stdout).toContain("step:Installing Node.js|apk add --no-cache nodejs npm"); + expect(result.stdout).toContain("finish-linux-node"); + expect(result.stdout).not.toContain("Installing Node.js via NodeSource"); + }); + it("clears npm freshness filters for package installs", () => { expect(script).toContain("env -u NPM_CONFIG_BEFORE -u npm_config_before"); expect(script).toContain('freshness_flag="--min-release-age=0"');