From ffa6cd888fba77aee2b6cc7ede810c6dd35bbb87 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 22 May 2026 15:15:22 +0100 Subject: [PATCH] fix(installer): extract portable Node with ZipFile --- CHANGELOG.md | 2 +- scripts/install.ps1 | 15 +++++++++++++-- test/scripts/install-ps1.test.ts | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 940f0b70faf..2642867a050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,7 +63,7 @@ Docs: https://docs.openclaw.ai - Plugins/discovery: strip `-plugin` package suffixes when deriving plugin id hints so package names line up with manifest ids. (#85170) Thanks @JulyanXu. - Telegram: preserve fenced code block languages through Markdown rendering so Telegram receives `language-*` code classes. (#85209) Thanks @leno23. - Windows installer: bootstrap a user-local portable Node.js when native Windows has no Node and no winget, Chocolatey, or Scoop, so first-run installs can continue on raw hosts. -- Windows installer: copy the downloaded portable Node.js directory into place so PowerShell 5.1 first-run bootstraps do not fail while moving zip contents. +- Windows installer: extract and copy the downloaded portable Node.js directory without PowerShell 5.1 `Expand-Archive`/move failures. - fix(integrations): enforce channel read target allowlists [AI]. (#84982) Thanks @pgondhi987. - Agents/heartbeat: route single-owner `session.dmScope=main` direct-message exec and cron event wakes back to the agent main session so async completions no longer strand context in orphan direct-DM queues. Fixes #71581. (#83743) Thanks @Kaspre. - Agents/code-mode: expose outer code-mode `exec` source through the `command` hook alias with `toolKind`/`toolInputKind` discriminators so exec-shaped policies can distinguish code-mode cells. (#83483) Thanks @Kaspre. diff --git a/scripts/install.ps1 b/scripts/install.ps1 index fe459799937..6996df81fd3 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -183,6 +183,18 @@ function Resolve-PortableNodeDownload { } } +function Expand-PortableNodeArchive { + param( + [Parameter(Mandatory = $true)] + [string]$ZipPath, + [Parameter(Mandatory = $true)] + [string]$DestinationPath + ) + + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($ZipPath, $DestinationPath) +} + function Install-PortableNode { if (Use-PortableNodeIfPresent) { Ensure-PortableNodeOnUserPath @@ -208,12 +220,11 @@ function Install-PortableNode { if (Test-Path $tmpExtract) { Remove-Item -Recurse -Force $tmpExtract } - New-Item -ItemType Directory -Force -Path $tmpExtract | Out-Null try { Write-Host " Downloading Node.js $($download.Version)..." -ForegroundColor Gray Invoke-WebRequest -UseBasicParsing -Uri $download.Url -OutFile $tmpZip - Expand-Archive -Path $tmpZip -DestinationPath $tmpExtract -Force + Expand-PortableNodeArchive -ZipPath $tmpZip -DestinationPath $tmpExtract $nodeDir = Get-ChildItem -Path $tmpExtract -Directory | Where-Object { Test-Path (Join-Path $_.FullName "node.exe") } | diff --git a/test/scripts/install-ps1.test.ts b/test/scripts/install-ps1.test.ts index 2250fda984e..9b196364742 100644 --- a/test/scripts/install-ps1.test.ts +++ b/test/scripts/install-ps1.test.ts @@ -121,6 +121,7 @@ describe("install.ps1 failure handling", () => { const userPathBody = extractFunctionBody(source, "Add-ToUserPath"); const depsRootBody = extractFunctionBody(source, "Get-OpenClawDepsRoot"); const resolveNodeBody = extractFunctionBody(source, "Resolve-PortableNodeDownload"); + const expandNodeBody = extractFunctionBody(source, "Expand-PortableNodeArchive"); expect(installNodeBody).toContain("Install-PortableNode"); expect(installNodeBody).toContain("Portable Node.js bootstrap failed"); @@ -137,6 +138,10 @@ describe("install.ps1 failure handling", () => { '[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")', ); expect(portableNodeBody).toContain("Invoke-WebRequest -UseBasicParsing"); + expect(portableNodeBody).toContain("Expand-PortableNodeArchive"); + expect(portableNodeBody).not.toContain("Expand-Archive"); + expect(portableNodeBody).not.toContain("New-Item -ItemType Directory -Force -Path $tmpExtract"); + expect(expandNodeBody).toContain("System.IO.Compression.ZipFile"); expect(resolveNodeBody).toContain("https://nodejs.org/dist/index.json"); expect(resolveNodeBody).toContain("win-$architecture-zip"); expect(resolveNodeBody).toContain("node-$($release.version)-win-$architecture.zip");