Skip to content

Commit 367d584

Browse files
committed
fix(installer): install node with apk on alpine
1 parent acfed37 commit 367d584

3 files changed

Lines changed: 69 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
88

99
### Fixes
1010

11+
- Installer: install Node.js through `apk` on Alpine Linux instead of falling through to the NodeSource package-manager path.
1112
- Installer: detect musl Linux shells such as Alpine as Linux instead of rejecting them before npm install.
1213
- 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.
1314
- 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.

scripts/install.sh

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,21 @@ check_node() {
16211621
fi
16221622
}
16231623

1624+
finish_linux_node_install() {
1625+
activate_supported_node_on_path || true
1626+
if ! node_is_at_least_required; then
1627+
local active_path active_version
1628+
active_path="$(command -v node 2>/dev/null || echo "not found")"
1629+
active_version="$(node -v 2>/dev/null || echo "missing")"
1630+
ui_error "Installed Node.js must be v${NODE_MIN_VERSION}+ but this shell is using ${active_version} (${active_path})"
1631+
echo "Upgrade the system Node.js package or install Node.js ${NODE_DEFAULT_MAJOR} manually, then rerun the installer."
1632+
exit 1
1633+
fi
1634+
1635+
ui_success "Node.js v$(node -v | cut -d'v' -f2) installed"
1636+
print_active_node_paths || true
1637+
}
1638+
16241639
# Install Node.js
16251640
install_node() {
16261641
if [[ "$OS" == "macos" ]]; then
@@ -1653,9 +1668,18 @@ install_node() {
16531668
else
16541669
run_quiet_step "Installing Node.js" sudo pacman -Sy --noconfirm nodejs npm
16551670
fi
1656-
promote_supported_node_binary || true
1657-
ui_success "Node.js v${NODE_DEFAULT_MAJOR} installed"
1658-
print_active_node_paths || true
1671+
finish_linux_node_install
1672+
return 0
1673+
fi
1674+
1675+
if command -v apk &> /dev/null; then
1676+
ui_info "Installing Node.js via apk (Alpine Linux detected)"
1677+
if is_root; then
1678+
run_quiet_step "Installing Node.js" apk add --no-cache nodejs npm
1679+
else
1680+
run_quiet_step "Installing Node.js" sudo apk add --no-cache nodejs npm
1681+
fi
1682+
finish_linux_node_install
16591683
return 0
16601684
fi
16611685

@@ -1699,9 +1723,7 @@ install_node() {
16991723
exit 1
17001724
fi
17011725

1702-
ui_success "Node.js v${NODE_DEFAULT_MAJOR} installed"
1703-
activate_supported_node_on_path || true
1704-
print_active_node_paths || true
1726+
finish_linux_node_install
17051727
fi
17061728
}
17071729

test/scripts/install-sh.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,46 @@ describe("install.sh", () => {
3939
expect(script).not.toContain('[[ "$OSTYPE" == "linux-gnu"* ]]');
4040
});
4141

42+
it("installs Node.js with apk on Alpine before falling back to NodeSource", () => {
43+
expect(script).toContain("finish_linux_node_install()");
44+
expect(script).toContain('ui_info "Installing Node.js via apk (Alpine Linux detected)"');
45+
expect(script).toContain(
46+
'run_quiet_step "Installing Node.js" apk add --no-cache nodejs npm',
47+
);
48+
expect(script).toContain(
49+
'run_quiet_step "Installing Node.js" sudo apk add --no-cache nodejs npm',
50+
);
51+
expect(script).toContain('if ! node_is_at_least_required; then');
52+
53+
const apkIndex = script.indexOf('if command -v apk &> /dev/null; then');
54+
const nodeSourceIndex = script.indexOf('ui_info "Installing Node.js via NodeSource"');
55+
expect(apkIndex).toBeGreaterThan(-1);
56+
expect(nodeSourceIndex).toBeGreaterThan(apkIndex);
57+
});
58+
59+
it("uses the apk Node.js installer path on Alpine", () => {
60+
const result = runInstallShell(`
61+
set -euo pipefail
62+
source "${SCRIPT_PATH}"
63+
OS=linux
64+
require_sudo() { :; }
65+
install_build_tools_linux() { return 0; }
66+
is_root() { return 0; }
67+
ui_info() { printf 'info:%s\\n' "$*"; }
68+
ui_success() { printf 'success:%s\\n' "$*"; }
69+
run_quiet_step() { printf 'step:%s|%s\\n' "$1" "\${*:2}"; }
70+
apk() { :; }
71+
finish_linux_node_install() { printf 'finish-linux-node\\n'; }
72+
install_node
73+
`);
74+
75+
expect(result.status).toBe(0);
76+
expect(result.stdout).toContain("info:Installing Node.js via apk (Alpine Linux detected)");
77+
expect(result.stdout).toContain("step:Installing Node.js|apk add --no-cache nodejs npm");
78+
expect(result.stdout).toContain("finish-linux-node");
79+
expect(result.stdout).not.toContain("Installing Node.js via NodeSource");
80+
});
81+
4282
it("clears npm freshness filters for package installs", () => {
4383
expect(script).toContain("env -u NPM_CONFIG_BEFORE -u npm_config_before");
4484
expect(script).toContain('freshness_flag="--min-release-age=0"');

0 commit comments

Comments
 (0)