



















@@ -109,14 +109,17 @@ describe("install.sh", () => {
109109110110it("installs Node.js with apk on Alpine before falling back to NodeSource", () => {
111111expect(script).toContain("finish_linux_node_install()");
112+expect(script).toContain("is_alpine_linux()");
113+expect(script).toContain("install_node_with_apk()");
112114expect(script).toContain('ui_info "Installing Node.js via apk (Alpine Linux detected)"');
113115expect(script).toContain('run_quiet_step "Installing Node.js" apk add --no-cache nodejs npm');
114116expect(script).toContain(
115117'run_quiet_step "Installing Node.js" sudo apk add --no-cache nodejs npm',
116118);
119+expect(script).toContain('run_quiet_step "Installing nodejs-current" apk add --no-cache nodejs-current npm');
117120expect(script).toContain("if ! node_is_at_least_required; then");
118121119-const apkIndex = script.indexOf("if command -v apk &> /dev/null; then");
122+const apkIndex = script.indexOf("if command -v apk &> /dev/null && is_alpine_linux; then");
120123const nodeSourceIndex = script.indexOf('ui_info "Installing Node.js via NodeSource"');
121124expect(apkIndex).toBeGreaterThan(-1);
122125expect(nodeSourceIndex).toBeGreaterThan(apkIndex);
@@ -130,10 +133,12 @@ describe("install.sh", () => {
130133 require_sudo() { :; }
131134 install_build_tools_linux() { return 0; }
132135 is_root() { return 0; }
136+ is_alpine_linux() { return 0; }
133137 ui_info() { printf 'info:%s\\n' "$*"; }
134138 ui_success() { printf 'success:%s\\n' "$*"; }
135139 run_quiet_step() { printf 'step:%s|%s\\n' "$1" "\${*:2}"; }
136140 apk() { :; }
141+ node_is_at_least_required() { return 0; }
137142 finish_linux_node_install() { printf 'finish-linux-node\\n'; }
138143 install_node
139144 `);
@@ -145,6 +150,182 @@ describe("install.sh", () => {
145150expect(result.stdout).not.toContain("Installing Node.js via NodeSource");
146151});
147152153+it("tries nodejs-current when Alpine nodejs is below the runtime floor", () => {
154+const result = runInstallShell(`
155+ set -euo pipefail
156+ source "${SCRIPT_PATH}"
157+ OS=linux
158+ NODE_FAKE_VERSION=v20.15.1
159+ require_sudo() { :; }
160+ install_build_tools_linux() { return 0; }
161+ is_root() { return 0; }
162+ is_alpine_linux() { return 0; }
163+ ui_info() { printf 'info:%s\\n' "$*"; }
164+ ui_success() { printf 'success:%s\\n' "$*"; }
165+ ui_warn() { printf 'warn:%s\\n' "$*"; }
166+ run_quiet_step() {
167+ printf 'step:%s|%s\\n' "$1" "\${*:2}"
168+ "\${@:2}"
169+ }
170+ apk() {
171+ printf 'apk:%s\\n' "$*"
172+ if [[ "$*" == *"nodejs-current"* ]]; then
173+ NODE_FAKE_VERSION=v22.22.2
174+ fi
175+ }
176+ node() {
177+ if [[ "\${1:-}" == "-v" ]]; then
178+ printf '%s\\n' "$NODE_FAKE_VERSION"
179+ fi
180+ }
181+ activate_supported_node_on_path() { :; }
182+ finish_linux_node_install() { printf 'finish-linux-node\\n'; }
183+ install_node
184+ `);
185+186+expect(result.status).toBe(0);
187+expect(result.stdout).toContain("step:Installing Node.js|apk add --no-cache nodejs npm");
188+expect(result.stdout).toContain("warn:Alpine nodejs package installed v20.15.1");
189+expect(result.stdout).toContain("step:Installing nodejs-current|apk add --no-cache nodejs-current npm");
190+expect(result.stdout).toContain("finish-linux-node");
191+});
192+193+it("fails with Alpine version guidance when apk cannot provide the runtime floor", () => {
194+const result = runInstallShell(`
195+ set -euo pipefail
196+ source "${SCRIPT_PATH}"
197+ OS=linux
198+ NODE_FAKE_VERSION=v20.15.1
199+ require_sudo() { :; }
200+ install_build_tools_linux() { return 0; }
201+ is_root() { return 0; }
202+ is_alpine_linux() { return 0; }
203+ ui_info() { printf 'info:%s\\n' "$*"; }
204+ ui_success() { printf 'success:%s\\n' "$*"; }
205+ ui_warn() { printf 'warn:%s\\n' "$*"; }
206+ ui_error() { printf 'error:%s\\n' "$*"; }
207+ run_quiet_step() {
208+ printf 'step:%s|%s\\n' "$1" "\${*:2}"
209+ "\${@:2}"
210+ }
211+ apk() {
212+ printf 'apk:%s\\n' "$*"
213+ if [[ "$*" == *"nodejs-current"* ]]; then
214+ NODE_FAKE_VERSION=v21.7.3
215+ fi
216+ }
217+ node() {
218+ if [[ "\${1:-}" == "-v" ]]; then
219+ printf '%s\\n' "$NODE_FAKE_VERSION"
220+ fi
221+ }
222+ activate_supported_node_on_path() { :; }
223+ install_node
224+ `);
225+226+expect(result.status).toBe(1);
227+expect(result.stdout).toContain("warn:Alpine nodejs package installed v20.15.1");
228+expect(result.stdout).toContain("step:Installing nodejs-current|apk add --no-cache nodejs-current npm");
229+expect(result.stdout).toContain("error:Alpine apk repositories did not provide Node.js v22.19+");
230+expect(result.stdout).toContain("Use Alpine 3.21+ or install Node.js 24 manually");
231+});
232+233+it("installs Git with apk on Alpine", () => {
234+const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-git-apk-"));
235+const bin = join(tmp, "bin");
236+const apkLog = join(tmp, "apk-args.txt");
237+mkdirSync(bin, { recursive: true });
238+const fakeApk = join(bin, "apk");
239+writeFileSync(
240+fakeApk,
241+[
242+"#!/usr/bin/env bash",
243+"set -euo pipefail",
244+`printf '%s\\n' "$*" >> ${JSON.stringify(apkLog)}`,
245+"",
246+].join("\n"),
247+);
248+chmodSync(fakeApk, 0o755);
249+250+try {
251+const result = runInstallShell(`
252+ set -euo pipefail
253+ source "${SCRIPT_PATH}"
254+ PATH=${JSON.stringify(`${bin}:/bin`)}
255+ OS=linux
256+ require_sudo() { :; }
257+ is_root() { return 0; }
258+ is_alpine_linux() { return 0; }
259+ ui_success() { printf 'success:%s\\n' "$*"; }
260+ ui_error() { printf 'error:%s\\n' "$*"; }
261+ run_quiet_step() {
262+ printf 'step:%s|%s\\n' "$1" "\${*:2}"
263+ "\${@:2}"
264+ }
265+ install_git
266+ `);
267+268+expect(result.status).toBe(0);
269+expect(result.stdout).toContain("step:Installing Git|apk add --no-cache git");
270+expect(result.stdout).toContain("success:Git installed");
271+expect(readFileSync(apkLog, "utf8").trim()).toBe("add --no-cache git");
272+} finally {
273+rmSync(tmp, { recursive: true, force: true });
274+}
275+});
276+277+it("does not select apk Git on non-Alpine hosts", () => {
278+const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-git-native-"));
279+const bin = join(tmp, "bin");
280+const apkLog = join(tmp, "apk-args.txt");
281+mkdirSync(bin, { recursive: true });
282+const fakeApk = join(bin, "apk");
283+const fakeApt = join(bin, "apt-get");
284+writeFileSync(apkLog, "");
285+writeFileSync(
286+fakeApk,
287+[
288+"#!/usr/bin/env bash",
289+"set -euo pipefail",
290+`printf '%s\\n' "$*" >> ${JSON.stringify(apkLog)}`,
291+"",
292+].join("\n"),
293+);
294+writeFileSync(fakeApt, "#!/usr/bin/env bash\nexit 0\n");
295+chmodSync(fakeApk, 0o755);
296+chmodSync(fakeApt, 0o755);
297+298+try {
299+const result = runInstallShell(`
300+ set -euo pipefail
301+ source "${SCRIPT_PATH}"
302+ PATH=${JSON.stringify(`${bin}:/bin`)}
303+ OS=linux
304+ require_sudo() { :; }
305+ is_root() { return 0; }
306+ is_alpine_linux() { return 1; }
307+ apt_get_update() { printf 'apt-update\\n'; }
308+ apt_get_install() { printf 'apt-install:%s\\n' "$*"; }
309+ ui_success() { printf 'success:%s\\n' "$*"; }
310+ ui_error() { printf 'error:%s\\n' "$*"; }
311+ run_quiet_step() {
312+ printf 'step:%s|%s\\n' "$1" "\${*:2}"
313+ "\${@:2}"
314+ }
315+ install_git
316+ `);
317+318+expect(result.status).toBe(0);
319+expect(result.stdout).toContain("step:Updating package index|apt_get_update");
320+expect(result.stdout).toContain("apt-update");
321+expect(result.stdout).toContain("step:Installing Git|apt_get_install git");
322+expect(result.stdout).toContain("apt-install:git");
323+expect(readFileSync(apkLog, "utf8")).toBe("");
324+} finally {
325+rmSync(tmp, { recursive: true, force: true });
326+}
327+});
328+148329it("clears npm freshness filters for package installs", () => {
149330expect(script).toContain("env -u NPM_CONFIG_BEFORE -u npm_config_before");
150331expect(script).toContain('freshness_flag="--min-release-age=0"');
@@ -156,10 +337,12 @@ describe("install.sh", () => {
156337it("does not emit --before when raw user npmrc config contains min-release-age", () => {
157338const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-npmrc-"));
158339const bin = join(tmp, "bin");
340+const home = join(tmp, "home");
159341const npmrc = join(tmp, "user.npmrc");
160342const calls = join(tmp, "npm-calls.txt");
161343const installArgs = join(tmp, "npm-install-args.txt");
162344mkdirSync(bin, { recursive: true });
345+mkdirSync(home, { recursive: true });
163346writeFileSync(npmrc, "min-release-age=7\n");
164347const fakeNpm = join(bin, "npm");
165348writeFileSync(
@@ -194,10 +377,11 @@ describe("install.sh", () => {
194377'printf "cmd=%s\\n" "$LAST_NPM_INSTALL_CMD"',
195378].join("\n"),
196379{
380+HOME: home,
197381NPM_CONFIG_USERCONFIG: npmrc,
198382NPM_FAKE_CALLS: calls,
199383NPM_FAKE_INSTALL_ARGS: installArgs,
200-PATH: `${bin}:${process.env.PATH}`,
384+PATH: `${bin}:/usr/local/bin:/usr/bin:/bin`,
201385},
202386);
203387@@ -638,6 +822,13 @@ describe("install.sh", () => {
638822[
639823`cd ${JSON.stringify(process.cwd())}`,
640824`source ${JSON.stringify(SCRIPT_PATH)}`,
825+"type() {",
826+' if [[ "$*" == "-P -a node" ]]; then',
827+` printf '%s\\n' ${JSON.stringify(staleNode)} ${JSON.stringify(supportedNode)}`,
828+" return 0",
829+" fi",
830+' builtin type "$@"',
831+"}",
641832"set +e",
642833"OS=linux",
643834"promote_supported_node_binary",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。