fix(installer): extract portable Node with ZipFile

This commit is contained in:
Peter Steinberger
2026-05-22 15:15:22 +01:00
parent 69255f8f32
commit ffa6cd888f
3 changed files with 19 additions and 3 deletions

View File

@@ -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.

View File

@@ -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") } |

View File

@@ -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");