diff --git a/build/preinstalled-skills/.preinstalled-lock.json b/build/preinstalled-skills/.preinstalled-lock.json index 5c52695..5a6df94 100644 --- a/build/preinstalled-skills/.preinstalled-lock.json +++ b/build/preinstalled-skills/.preinstalled-lock.json @@ -1,45 +1,45 @@ { - "generatedAt": "2026-04-19T12:02:30.404Z", + "generatedAt": "2026-04-27T03:36:36.328Z", "skills": [ { "slug": "pdf", - "version": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c", + "version": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9", "repo": "anthropics/skills", "repoPath": "skills/pdf", "ref": "main", - "commit": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c" + "commit": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9" }, { "slug": "xlsx", - "version": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c", + "version": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9", "repo": "anthropics/skills", "repoPath": "skills/xlsx", "ref": "main", - "commit": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c" + "commit": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9" }, { "slug": "docx", - "version": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c", + "version": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9", "repo": "anthropics/skills", "repoPath": "skills/docx", "ref": "main", - "commit": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c" + "commit": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9" }, { "slug": "pptx", - "version": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c", + "version": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9", "repo": "anthropics/skills", "repoPath": "skills/pptx", "ref": "main", - "commit": "2c7ec5e78b8e5d43ea02e90bb8826f6b9f147b0c" + "commit": "5128e1865d670f5d6c9cef000e6dfc4e951fb5b9" }, { "slug": "find-skills", - "version": "bc21a37a12b90fcb5aec051c91baf5b227b704b1", + "version": "5516b8ad07393f35af4b50238b9d3f7cceca5f1e", "repo": "vercel-labs/skills", "repoPath": "skills/find-skills", "ref": "main", - "commit": "bc21a37a12b90fcb5aec051c91baf5b227b704b1" + "commit": "5516b8ad07393f35af4b50238b9d3f7cceca5f1e" }, { "slug": "self-improving-agent", diff --git a/dist/index.html b/dist/index.html index dc28771..be480ed 100644 --- a/dist/index.html +++ b/dist/index.html @@ -8,8 +8,8 @@ http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: http://8.138.234.141 https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn" /> - - + +
diff --git a/docs/Desktop-Packaging-Workflow.md b/docs/Desktop-Packaging-Workflow.md new file mode 100644 index 0000000..0cd14f3 --- /dev/null +++ b/docs/Desktop-Packaging-Workflow.md @@ -0,0 +1,344 @@ +# 桌面端打包流程 + +本文用于梳理 `zn-ai` 当前仓库的 Windows / macOS 打包流程,说明应该先执行哪个脚本、每个脚本会自动带哪些前置步骤,以及常见使用场景。 + +## 1. 先记结论 + +如果你只是想正常出包,不需要手动先跑 `bundle-openclaw`、`bundle:preinstalled-skills`、`uv:download:*`。 + +直接按目标平台执行: + +- Windows 安装包:`pnpm run package:win` +- mac 双架构包:`pnpm run package:mac` +- mac Intel 单架构包:`pnpm run package:mac:x64` +- mac Apple Silicon 单架构包:`pnpm run package:mac:arm64` + +也就是说: + +- 要打 Windows 包,优先跑 `package:win` +- 要打 mac 包,优先跑 `package:mac` 或具体架构脚本 +- `package` 只是“准备打包产物”,不是最终安装包命令 +- `build` 是“当前宿主机默认 electron-builder 打包”,不如平台脚本直观,日常不建议优先用 + +## 2. 推荐执行顺序 + +### 2.1 第一次打包或切分支后 + +1. `pnpm install` +2. `pnpm typecheck` +3. 按目标平台执行对应脚本 + +推荐命令: + +```powershell +pnpm install +pnpm typecheck +pnpm run package:win +``` + +或: + +```powershell +pnpm install +pnpm typecheck +pnpm run package:mac +``` + +### 2.2 日常出 Windows 包 + +```powershell +pnpm run package:win +``` + +### 2.3 日常出 mac 包 + +如果希望一次出 x64 + arm64: + +```powershell +pnpm run package:mac +``` + +如果只想出单一架构: + +```powershell +pnpm run package:mac:x64 +``` + +或: + +```powershell +pnpm run package:mac:arm64 +``` + +## 3. 各脚本职责 + +### 3.1 `pnpm run package` + +这是“打包前准备阶段”,会自动执行: + +1. `prepackage` +2. `vite build` +3. `node scripts/bundle-openclaw.mjs` +4. `zx scripts/bundle-preinstalled-skills.mjs` + +所以它会完成: + +- 检查并补齐平台所需的 bundled runtime binaries +- 生成 `dist` 和 `dist-electron` +- 生成 `build/openclaw` +- 生成 `build/preinstalled-skills` + +但是它不会直接产出最终安装包。 + +### 3.2 `pnpm run package:win` + +等价于: + +```powershell +pnpm run package +electron-builder --win --publish never +``` + +用途: + +- 产出 Windows NSIS 安装包 +- 当前配置只打 `x64` + +### 3.3 `pnpm run package:mac` + +等价于: + +```powershell +pnpm run package +electron-builder --mac --publish never +``` + +用途: + +- 产出 macOS 包 +- 当前配置目标包含 `dmg` 和 `zip` +- 当前配置默认覆盖 `x64` 和 `arm64` + +### 3.4 `pnpm run build` + +等价于: + +```powershell +pnpm run package +electron-builder +``` + +特点: + +- 使用 `electron-builder.yml` 默认目标 +- 更适合“按宿主机默认配置整体打一遍” +- 不如 `package:win` / `package:mac` 明确 + +## 4. 打包时自动发生的事 + +### 4.1 `prepackage` + +`prepackage` 会自动执行: + +```powershell +node scripts/ensure-bundled-runtime-binaries.mjs +``` + +这一步会按当前平台检查 `resources/bin` 下的运行时文件是否齐全。 + +当前规则: + +- Windows:检查 `uv.exe` 和 `node.exe` +- macOS:检查 `uv` +- Linux:检查 `uv` + +如果缺失,会自动调用对应下载脚本: + +- Windows:`uv:download:win`、`node:download:win` +- macOS:`uv:download:mac` +- Linux:`uv:download:linux` + +因此正常情况下,你不需要手动先跑这些下载脚本。 + +### 4.2 `afterPack` + +`electron-builder` 打包后会自动执行: + +```powershell +scripts/after-pack.cjs +``` + +它会继续完成这些动作: + +- 把 `resources/bin//...` 平铺复制到安装包里的 `resources/bin/` +- 把 `electron/scripts` 处理到包内 +- 补拷 `playwright`、`playwright-core`、`chromium-bidi`、`bytenode` +- 补拷 `build/openclaw` +- 清理不必要的开发文件,缩小包体 + +所以如果你是正常走 `package:win` / `package:mac`,这些都已经自动串好了。 + +## 5. 什么时候手动跑单独脚本 + +### 5.1 手动预拉 Windows 运行时 + +如果你只是想提前把 Windows 运行时二进制准备好,可以执行: + +```powershell +pnpm run prep:win-binaries +``` + +它会跑: + +```powershell +pnpm run uv:download:win +pnpm run node:download:win +``` + +适合场景: + +- 网络较慢,想在正式打包前先把依赖下载好 +- 排查 `resources/bin/win32-*` 是否齐全 + +### 5.2 手动更新预装 skills + +如果你只想刷新预装 skills,不是正式出包,可以执行: + +```powershell +pnpm run bundle:preinstalled-skills +``` + +### 5.3 手动更新 OpenClaw 运行时 + +如果你只想刷新 `build/openclaw`,可以执行: + +```powershell +pnpm run bundle:openclaw +``` + +## 6. 产物位置 + +最终安装包 / 压缩包输出目录: + +```text +release/ +``` + +打包中间产物目录: + +```text +dist/ +dist-electron/ +build/openclaw/ +build/preinstalled-skills/ +``` + +## 7. Windows / mac 实操建议 + +### 7.1 Windows + +推荐最简流程: + +```powershell +pnpm install +pnpm typecheck +pnpm run package:win +``` + +说明: + +- 当前 `electron-builder.yml` 的 Windows 目标是 `nsis` +- 当前只配置了 `x64` + +### 7.2 macOS + +推荐最简流程: + +```powershell +pnpm install +pnpm typecheck +pnpm run package:mac +``` + +如果只打单架构: + +```powershell +pnpm run package:mac:x64 +``` + +或: + +```powershell +pnpm run package:mac:arm64 +``` + +如果只是本地快速验包,不追求最大压缩率: + +```powershell +pnpm run package:mac:fast +``` + +如果想保留 `afterPack` 清理前的更多内容,便于本地排查: + +```powershell +pnpm run package:mac:local +``` + +说明: + +- `package:mac:local` 会设置 `SKIP_AFTERPACK_CLEANUP=1` +- 它不是跳过打包,而是跳过 `afterPack` 里的清理步骤 + +## 8. 常见误区 + +### 误区 1:先手动跑 `bundle-openclaw` 才能打包 + +不是必须。 + +因为 `package` 已经会自动执行: + +- `node scripts/bundle-openclaw.mjs` +- `zx scripts/bundle-preinstalled-skills.mjs` + +### 误区 2:先手动跑 `uv:download:win` 才能打 Windows 包 + +也不是必须。 + +因为 `prepackage` 会自动执行 `ensure-bundled-runtime-binaries.mjs`,缺失时会自动下载。 + +### 误区 3:`package` 就是最终出包命令 + +不是。 + +`package` 只做准备,不直接生成最终安装包;真正平台出包应使用: + +- `package:win` +- `package:mac` +- `package:mac:x64` +- `package:mac:arm64` + +## 9. 建议你平时直接记住的命令 + +### Windows + +```powershell +pnpm run package:win +``` + +### macOS + +```powershell +pnpm run package:mac +``` + +### 只做前置产物准备 + +```powershell +pnpm run package +``` + +### 只想检查类型 + +```powershell +pnpm typecheck +``` diff --git a/electron-builder.yml b/electron-builder.yml index a45c99f..3e95341 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -48,6 +48,9 @@ afterPack: ./scripts/after-pack.cjs # macOS Configuration mac: + extraResources: + - from: resources/cli/posix/ + to: cli/ category: public.app-category.productivity icon: resources/icons/icon.icns target: @@ -86,6 +89,9 @@ dmg: win: verifyUpdateCodeSignature: false signAndEditExecutable: false + extraResources: + - from: resources/cli/win32/ + to: cli/ icon: resources/icons/icon.ico target: - target: nsis @@ -108,6 +114,9 @@ nsis: # Linux Configuration linux: + extraResources: + - from: resources/cli/posix/ + to: cli/ icon: resources/icons/ target: - target: AppImage diff --git a/package.json b/package.json index b93dba8..5cf2d63 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "dist-electron/main/main.js", "scripts": { "init": "pnpm install && pnpm run uv:download", - "prepackage": "node scripts/ensure-bundled-runtime-binaries.mjs", + "prepackage": "node scripts/ensure-bundled-runtime-binaries.mjs && node scripts/patch-electron-builder-nsis.mjs", "predev": "zx scripts/prepare-preinstalled-skills-dev.mjs", "prestart": "zx scripts/prepare-preinstalled-skills-dev.mjs", "dev": "vite", @@ -40,7 +40,7 @@ "generate-prod-entry": "node build/scripts/generateProdEntry.js", "clean": "node build/scripts/clean.js", "build:encrypt": "pnpm run clean && pnpm run build:vite && pnpm run package", - "postinstall": "electron-builder install-app-deps" + "postinstall": "electron-builder install-app-deps && node scripts/patch-electron-builder-nsis.mjs" }, "keywords": [], "author": { diff --git a/resources/cli/posix/openclaw b/resources/cli/posix/openclaw new file mode 100644 index 0000000..f176c74 --- /dev/null +++ b/resources/cli/posix/openclaw @@ -0,0 +1,54 @@ +#!/bin/sh +# OpenClaw CLI - managed by NIANXX +# Do not edit manually. Regenerated on NIANXX updates. + +# Resolve the real path of this script (follow symlinks) +SCRIPT="$0" +while [ -L "$SCRIPT" ]; do + SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)" + SCRIPT="$(readlink "$SCRIPT")" + [ "${SCRIPT#/}" = "$SCRIPT" ] && SCRIPT="$SCRIPT_DIR/$SCRIPT" +done +SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT")" && pwd)" + +if [ "$(uname)" = "Darwin" ]; then + # macOS: .app/Contents/Resources/cli/openclaw + # SCRIPT_DIR = .../Contents/Resources/cli + CONTENTS_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" + ELECTRON="$CONTENTS_DIR/MacOS/NIANXX" + CLI="$CONTENTS_DIR/Resources/openclaw/openclaw.mjs" +else + # Linux: /opt/NIANXX/resources/cli/openclaw + # SCRIPT_DIR = .../resources/cli + INSTALL_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" + CLI="$INSTALL_DIR/resources/openclaw/openclaw.mjs" + + if [ -x "$INSTALL_DIR/nianxx" ]; then + ELECTRON="$INSTALL_DIR/nianxx" + elif [ -x "$INSTALL_DIR/NIANXX" ]; then + ELECTRON="$INSTALL_DIR/NIANXX" + else + ELECTRON="$INSTALL_DIR/zn-ai" + fi +fi + +if [ ! -f "$ELECTRON" ]; then + echo "Error: NIANXX executable not found at $ELECTRON" >&2 + echo "Please reinstall NIANXX or remove this script: $0" >&2 + exit 1 +fi + +case "$1" in + update) + echo "openclaw is managed by NIANXX (bundled version)." + echo "" + echo "To update openclaw, update NIANXX:" + echo " Open NIANXX > Settings > Check for Updates" + echo "" + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --version 2>/dev/null || true + exit 0 + ;; +esac + +export OPENCLAW_EMBEDDED_IN="NIANXX" +ELECTRON_RUN_AS_NODE=1 exec "$ELECTRON" "$CLI" "$@" diff --git a/resources/cli/win32/openclaw b/resources/cli/win32/openclaw new file mode 100644 index 0000000..1464adf --- /dev/null +++ b/resources/cli/win32/openclaw @@ -0,0 +1,26 @@ +#!/bin/sh +# OpenClaw CLI wrapper for Git Bash / MSYS2 on Windows +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INSTALL_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +case "$1" in + update) + echo "openclaw is managed by NIANXX (bundled version)." + echo "" + echo "To update openclaw, update NIANXX:" + echo " Open NIANXX > Settings > Check for Updates" + exit 0 + ;; +esac + +export OPENCLAW_EMBEDDED_IN="NIANXX" +NODE_EXE="$INSTALL_DIR/resources/bin/node.exe" +OPENCLAW_ENTRY="$INSTALL_DIR/resources/openclaw/openclaw.mjs" + +if [ -f "$NODE_EXE" ]; then + if "$NODE_EXE" -e 'const [maj,min]=process.versions.node.split(".").map(Number);process.exit((maj>22||maj===22&&min>=16)?0:1)' >/dev/null 2>&1; then + exec "$NODE_EXE" "$OPENCLAW_ENTRY" "$@" + fi +fi + +ELECTRON_RUN_AS_NODE=1 exec "$INSTALL_DIR/NIANXX.exe" "$OPENCLAW_ENTRY" "$@" diff --git a/resources/cli/win32/openclaw.cmd b/resources/cli/win32/openclaw.cmd new file mode 100644 index 0000000..939f36e --- /dev/null +++ b/resources/cli/win32/openclaw.cmd @@ -0,0 +1,37 @@ +@echo off +setlocal + +if /i "%1"=="update" ( + echo openclaw is managed by NIANXX ^(bundled version^). + echo. + echo To update openclaw, update NIANXX: + echo Open NIANXX ^> Settings ^> Check for Updates + exit /b 0 +) + +rem Switch console to UTF-8 so Unicode box-drawing and CJK text render correctly +rem on non-English Windows (e.g. Chinese CP936). Save the previous codepage to restore later. +for /f "tokens=2 delims=:." %%a in ('chcp') do set /a "_CP=%%a" 2>nul +chcp 65001 >nul 2>&1 + +set OPENCLAW_EMBEDDED_IN=NIANXX +set "NODE_EXE=%~dp0..\bin\node.exe" +set "OPENCLAW_ENTRY=%~dp0..\openclaw\openclaw.mjs" + +set "_USE_BUNDLED_NODE=0" +if exist "%NODE_EXE%" ( + "%NODE_EXE%" -e "const [maj,min]=process.versions.node.split('.').map(Number);process.exit((maj>22||maj===22&&min>=16)?0:1)" >nul 2>&1 + if not errorlevel 1 set "_USE_BUNDLED_NODE=1" +) + +if "%_USE_BUNDLED_NODE%"=="1" ( + "%NODE_EXE%" "%OPENCLAW_ENTRY%" %* +) else ( + set ELECTRON_RUN_AS_NODE=1 + "%~dp0..\..\NIANXX.exe" "%OPENCLAW_ENTRY%" %* +) +set _EXIT=%ERRORLEVEL% + +if defined _CP chcp %_CP% >nul 2>&1 + +endlocal & exit /b %_EXIT% diff --git a/resources/cli/win32/update-user-path.ps1 b/resources/cli/win32/update-user-path.ps1 new file mode 100644 index 0000000..3b5204b --- /dev/null +++ b/resources/cli/win32/update-user-path.ps1 @@ -0,0 +1,150 @@ +param( + [Parameter(Mandatory = $true)] + [ValidateSet('add', 'remove')] + [string]$Action, + + [Parameter(Mandatory = $true)] + [string]$CliDir +) + +$ErrorActionPreference = 'Stop' + +function Get-UserPathRegistryValue { + $raw = [Environment]::GetEnvironmentVariable('Path', 'User') + $kind = [Microsoft.Win32.RegistryValueKind]::ExpandString + + try { + $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $false) + if ($null -ne $key) { + try { + $stored = $key.GetValue('Path', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + if ($null -ne $stored) { + $raw = [string]$stored + } + } catch { + # Fallback to Environment API value + } + + try { + $kind = $key.GetValueKind('Path') + } catch { + # Keep default ExpandString + } + $key.Close() + } + } catch { + # Fallback to Environment API value + } + + return @{ + Raw = $raw + Kind = $kind + } +} + +function Normalize-PathEntry { + param([string]$Value) + + if ([string]::IsNullOrWhiteSpace($Value)) { + return '' + } + + return $Value.Trim().Trim('"').TrimEnd('\').ToLowerInvariant() +} + +$pathMeta = Get-UserPathRegistryValue +$current = $pathMeta.Raw +$entries = @() +if (-not [string]::IsNullOrWhiteSpace($current)) { + $entries = $current -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } +} + +$target = Normalize-PathEntry $CliDir +$seen = [System.Collections.Generic.HashSet[string]]::new() +$nextEntries = New-Object System.Collections.Generic.List[string] + +foreach ($entry in $entries) { + $normalized = Normalize-PathEntry $entry + if ([string]::IsNullOrWhiteSpace($normalized)) { + continue + } + + if ($normalized -eq $target) { + continue + } + + if ($seen.Add($normalized)) { + $nextEntries.Add($entry.Trim().Trim('"')) + } +} + +$status = 'already-present' +if ($Action -eq 'add') { + if ($seen.Add($target)) { + $nextEntries.Add($CliDir) + $status = 'updated' + } +} elseif ($entries.Count -ne $nextEntries.Count) { + $status = 'updated' +} + +$isLikelyCorruptedWrite = ( + $Action -eq 'add' -and + $entries.Count -gt 1 -and + $nextEntries.Count -le 1 +) +if ($isLikelyCorruptedWrite) { + throw "Refusing to rewrite user PATH: input had $($entries.Count) entries but output has $($nextEntries.Count)." +} + +$newPath = if ($nextEntries.Count -eq 0) { $null } else { $nextEntries -join ';' } +try { + $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true) + if ($null -eq $key) { + throw 'Unable to open HKCU\Environment for write.' + } + + if ([string]::IsNullOrWhiteSpace($newPath)) { + $key.DeleteValue('Path', $false) + } else { + $kind = if ($pathMeta.Kind -eq [Microsoft.Win32.RegistryValueKind]::String) { + [Microsoft.Win32.RegistryValueKind]::String + } else { + [Microsoft.Win32.RegistryValueKind]::ExpandString + } + $key.SetValue('Path', $newPath, $kind) + } + $key.Close() +} catch { + throw "Failed to write HKCU\\Environment\\Path: $($_.Exception.Message)" +} + +try { + Add-Type -Namespace OpenClaw -Name NativeMethods -MemberDefinition @" +[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] +public static extern System.IntPtr SendMessageTimeout( + System.IntPtr hWnd, + int Msg, + System.IntPtr wParam, + string lParam, + int fuFlags, + int uTimeout, + out System.IntPtr lpdwResult +); +"@ + + $result = [IntPtr]::Zero + [OpenClaw.NativeMethods]::SendMessageTimeout( + [IntPtr]0xffff, + 0x001A, + [IntPtr]::Zero, + 'Environment', + 0x0002, + 5000, + [ref]$result + ) | Out-Null +} catch { + Write-Warning "PATH updated but failed to broadcast environment change: $($_.Exception.Message)" +} + +Write-Output $status diff --git a/scripts/bundle-preinstalled-skills.mjs b/scripts/bundle-preinstalled-skills.mjs index 96a0faf..38a3fc0 100644 --- a/scripts/bundle-preinstalled-skills.mjs +++ b/scripts/bundle-preinstalled-skills.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env zx import 'zx/globals'; +import { configureZxShellForPlatform } from './configure-zx-shell.mjs'; import { readFileSync, existsSync, mkdirSync, rmSync, cpSync, writeFileSync } from 'node:fs'; import { join, dirname, basename } from 'node:path'; import { fileURLToPath } from 'node:url'; +configureZxShellForPlatform(); + const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); const MANIFEST_PATH = join(ROOT, 'resources', 'skills', 'preinstalled-manifest.json'); diff --git a/scripts/configure-zx-shell.mjs b/scripts/configure-zx-shell.mjs new file mode 100644 index 0000000..8779b63 --- /dev/null +++ b/scripts/configure-zx-shell.mjs @@ -0,0 +1,7 @@ +import { usePowerShell } from 'zx'; + +export function configureZxShellForPlatform() { + if (process.platform === 'win32') { + usePowerShell(); + } +} diff --git a/scripts/download-bundled-node.mjs b/scripts/download-bundled-node.mjs index f778bc0..0cb5a1a 100644 --- a/scripts/download-bundled-node.mjs +++ b/scripts/download-bundled-node.mjs @@ -1,6 +1,9 @@ #!/usr/bin/env zx import 'zx/globals'; +import { configureZxShellForPlatform } from './configure-zx-shell.mjs'; + +configureZxShellForPlatform(); const ROOT_DIR = path.resolve(__dirname, '..'); const NODE_VERSION = '22.16.0'; @@ -130,4 +133,4 @@ try { echo(chalk.yellow` Packaging will continue without external Node.js binary.`); // Exit with code 0 to allow packaging to continue process.exit(0); -} \ No newline at end of file +} diff --git a/scripts/download-bundled-uv.mjs b/scripts/download-bundled-uv.mjs index 51aa0ca..169c931 100644 --- a/scripts/download-bundled-uv.mjs +++ b/scripts/download-bundled-uv.mjs @@ -1,6 +1,9 @@ #!/usr/bin/env zx import 'zx/globals'; +import { configureZxShellForPlatform } from './configure-zx-shell.mjs'; + +configureZxShellForPlatform(); const ROOT_DIR = path.resolve(__dirname, '..'); const UV_VERSION = '0.10.0'; diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs index 6585ec5..671ae61 100644 --- a/scripts/generate-icons.mjs +++ b/scripts/generate-icons.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env zx import 'zx/globals'; +import { configureZxShellForPlatform } from './configure-zx-shell.mjs'; import sharp from 'sharp'; import png2icons from 'png2icons'; import { fileURLToPath } from 'url'; +configureZxShellForPlatform(); + // Calculate paths const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/scripts/patch-electron-builder-nsis.mjs b/scripts/patch-electron-builder-nsis.mjs new file mode 100644 index 0000000..e4537da --- /dev/null +++ b/scripts/patch-electron-builder-nsis.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const targetFile = require.resolve('app-builder-lib/out/targets/nsis/NsisTarget.js'); +const patchMarker = 'zn-ai patch: retry NSIS stub execution on Windows'; + +const helperSnippet = ` +// ${patchMarker} +function isRetryableWindowsSpawnError(error) { + const code = error != null && typeof error.code === "string" ? error.code : ""; + const message = error != null && typeof error.message === "string" ? error.message : ""; + return (code === "EPERM" || + code === "EACCES" || + message.includes("spawn EPERM") || + message.includes("spawn EACCES") || + message.includes("used by another process")); +} +async function execWineWithRetry(file, file64 = null, appArgs = [], options = {}) { + const maxAttempts = process.platform === "win32" ? 12 : 1; + let attempt = 0; + while (true) { + attempt++; + try { + return await (0, wine_1.execWine)(file, file64, appArgs, options); + } + catch (error) { + const shouldRetry = process.platform === "win32" && + attempt < maxAttempts && + isRetryableWindowsSpawnError(error); + if (!shouldRetry) { + throw error; + } + builder_util_1.log.warn({ + attempt, + maxAttempts, + file, + reason: error.message || String(error), + }, "retrying NSIS stub execution after transient Windows spawn failure"); + await new Promise(resolve => setTimeout(resolve, attempt * 1000)); + } + } +} +`; + +const originalCall = 'await (0, wine_1.execWine)(installerPath, null, [], { env: { __COMPAT_LAYER: "RunAsInvoker" } });'; +const patchedCall = 'await execWineWithRetry(installerPath, null, [], { env: { __COMPAT_LAYER: "RunAsInvoker" } });'; + +const source = fs.readFileSync(targetFile, 'utf8'); + +if (source.includes(patchMarker)) { + console.log(`[patch-electron-builder-nsis] already patched: ${targetFile}`); + process.exit(0); +} + +if (!source.includes(originalCall)) { + console.error(`[patch-electron-builder-nsis] target call not found in ${targetFile}`); + process.exit(1); +} + +const classToken = 'class NsisTarget extends core_1.Target {'; +if (!source.includes(classToken)) { + console.error(`[patch-electron-builder-nsis] class token not found in ${targetFile}`); + process.exit(1); +} + +const patchedSource = source + .replace(classToken, `${helperSnippet}\n${classToken}`) + .replace(originalCall, patchedCall); + +fs.writeFileSync(targetFile, patchedSource, 'utf8'); +console.log(`[patch-electron-builder-nsis] patched ${targetFile}`); diff --git a/scripts/prepare-preinstalled-skills-dev.mjs b/scripts/prepare-preinstalled-skills-dev.mjs index 017c88d..f14361d 100644 --- a/scripts/prepare-preinstalled-skills-dev.mjs +++ b/scripts/prepare-preinstalled-skills-dev.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env zx import 'zx/globals'; +import { configureZxShellForPlatform } from './configure-zx-shell.mjs'; import { existsSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +configureZxShellForPlatform(); + const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); const lockPath = join(ROOT, 'build', 'preinstalled-skills', '.preinstalled-lock.json');