diff --git a/scripts/e2e/parallels/npm-update-scripts.ts b/scripts/e2e/parallels/npm-update-scripts.ts index 1748823c4e16..a2bdea402fc9 100644 --- a/scripts/e2e/parallels/npm-update-scripts.ts +++ b/scripts/e2e/parallels/npm-update-scripts.ts @@ -275,51 +275,47 @@ ${windowsScopedEnvFunction} function Remove-FuturePluginEntries { $configPath = Join-Path $env:USERPROFILE '.openclaw\\openclaw.json' if (-not (Test-Path $configPath)) { return } - try { $config = Get-Content $configPath -Raw | ConvertFrom-Json } catch { return } - $plugins = Get-OpenClawJsonProperty $config 'plugins' - if ($null -eq $plugins) { return } - $entries = Get-OpenClawJsonProperty $plugins 'entries' - if ($null -ne $entries) { - foreach ($pluginId in @('feishu', 'whatsapp', 'openai')) { - Remove-OpenClawJsonProperty $entries $pluginId + $nodeScript = @' +const fs = require("node:fs"); +const configPath = process.argv[2]; +let config; +try { + config = JSON.parse(fs.readFileSync(configPath, "utf8").replace(/^\\uFEFF/u, "")); +} catch { + process.exit(0); +} +const plugins = config.plugins; +if (!plugins || typeof plugins !== "object") { + process.exit(0); +} +const futurePluginIds = new Set(["feishu", "whatsapp", "openai"]); +let changed = false; +if (plugins.entries && typeof plugins.entries === "object") { + for (const pluginId of futurePluginIds) { + if (Object.hasOwn(plugins.entries, pluginId)) { + delete plugins.entries[pluginId]; + changed = true; } } - $allow = Get-OpenClawJsonProperty $plugins 'allow' - if ($null -ne $allow) { - Set-OpenClawJsonProperty $plugins 'allow' @($allow | Where-Object { $_ -notin @('feishu', 'whatsapp', 'openai') }) - } - $config | ConvertTo-Json -Depth 100 | Set-Content -Path $configPath -Encoding UTF8 } -function Get-OpenClawJsonProperty { - param([object]$Object, [string]$Name) - if ($null -eq $Object) { return $null } - if ($Object -is [System.Collections.IDictionary]) { return $Object[$Name] } - $property = $Object.PSObject.Properties[$Name] - if ($null -eq $property) { return $null } - return $property.Value +if (Array.isArray(plugins.allow)) { + const allow = plugins.allow.filter((pluginId) => !futurePluginIds.has(pluginId)); + if (allow.length !== plugins.allow.length) { + plugins.allow = allow; + changed = true; + } } -function Set-OpenClawJsonProperty { - param([object]$Object, [string]$Name, [object]$Value) - if ($Object -is [System.Collections.IDictionary]) { - $Object[$Name] = $Value - return - } - $property = $Object.PSObject.Properties[$Name] - if ($null -ne $property) { - $property.Value = $Value - return - } - $Object | Add-Member -NotePropertyName $Name -NotePropertyValue $Value +if (changed) { + fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\\n"); } -function Remove-OpenClawJsonProperty { - param([object]$Object, [string]$Name) - if ($null -eq $Object) { return } - if ($Object -is [System.Collections.IDictionary]) { - if ($Object.Contains($Name)) { $Object.Remove($Name) } - return - } - if ($null -ne $Object.PSObject.Properties[$Name]) { - $Object.PSObject.Properties.Remove($Name) +'@ + $nodeScriptPath = Join-Path ([System.IO.Path]::GetTempPath()) ('openclaw-future-plugin-scrub-' + [guid]::NewGuid().ToString('N') + '.cjs') + try { + $nodeScript | Set-Content -Path $nodeScriptPath -Encoding UTF8 + & node.exe $nodeScriptPath $configPath + if ($LASTEXITCODE -ne 0) { throw "failed to scrub future plugin entries" } + } finally { + Remove-Item $nodeScriptPath -Force -ErrorAction SilentlyContinue } } function Stop-OpenClawGatewayProcesses { diff --git a/test/scripts/parallels-npm-update-smoke.test.ts b/test/scripts/parallels-npm-update-smoke.test.ts index c8d5059065c7..3cfc89b343b7 100644 --- a/test/scripts/parallels-npm-update-smoke.test.ts +++ b/test/scripts/parallels-npm-update-smoke.test.ts @@ -846,6 +846,11 @@ exit 7 it("scrubs future plugin entries before invoking old same-guest updaters", () => { const script = readFileSync(UPDATE_SCRIPTS_PATH, "utf8"); + const windowsScript = windowsUpdateScript({ + auth: TEST_AUTH, + expectedNeedle: "2026.5.3-beta.2", + updateTarget: "2026.5.3-beta.2", + }); const macosScript = macosUpdateScript({ auth: TEST_AUTH, expectedNeedle: "2026.5.3-beta.2", @@ -856,9 +861,18 @@ exit 7 expect(script).toContain("scrub_future_plugin_entries"); expect(script).toContain("delete plugins.entries.feishu"); expect(script).toContain("delete plugins.entries.whatsapp"); - expect(script).toContain("if ($null -ne $allow)"); - expect(script).not.toContain("if ($allow -is [array])"); - expect(script).toContain("Remove-FuturePluginEntries\nStop-OpenClawGatewayProcesses"); + expect(windowsScript).toContain( + 'const futurePluginIds = new Set(["feishu", "whatsapp", "openai"])', + ); + expect(windowsScript).toContain('replace(/^\\uFEFF/u, "")'); + expect(windowsScript).toContain("if (allow.length !== plugins.allow.length)"); + expect(windowsScript).toContain('JSON.stringify(config, null, 2) + "\\n"'); + expect(windowsScript).not.toContain("ConvertTo-Json -Depth 100"); + expect(windowsScript).toContain("& node.exe $nodeScriptPath $configPath"); + expect(windowsScript).toContain( + "Remove-Item $nodeScriptPath -Force -ErrorAction SilentlyContinue", + ); + expect(windowsScript).toContain("Remove-FuturePluginEntries\nStop-OpenClawGatewayProcesses"); expect(script).toContain("scrub_future_plugin_entries\nstop_openclaw_gateway_processes"); expect(script).toContain("Invoke-WithScopedEnv @{ OPENCLAW_DISABLE_BUNDLED_PLUGINS = '1'"); expect(macosScript).toContain('OPENCLAW_BIN="$(resolve_required_command openclaw)"'); diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index 00ed889bd82a..ef3b9a92ff95 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -1399,9 +1399,10 @@ if (isPrlctl) { const script = readFileSync(TS_PATHS.npmUpdateScripts, "utf8"); expect(script).not.toContain("ConvertFrom-Json -AsHashtable"); - expect(script).toContain("function Get-OpenClawJsonProperty"); - expect(script).toContain("function Remove-OpenClawJsonProperty"); - expect(script).toContain("Remove-OpenClawJsonProperty $entries $pluginId"); + expect(script).not.toContain("ConvertTo-Json -Depth 100"); + expect(script).toContain('replace(/^\\\\uFEFF/u, "")'); + expect(script).toContain("$nodeScript | Set-Content -Path $nodeScriptPath -Encoding UTF8"); + expect(script).toContain("& node.exe $nodeScriptPath $configPath"); }); it("keeps aggregate update guest scripts isolated from the npm-update orchestrator", () => {