Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 83 additions & 19 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ jobs:
# Windows x64
- os: windows-latest
arch: x64
build_cmd: pnpm run build:win
build_cmd: pnpm run build:win:x64
signing_identity: ''
# Windows ARM64
- os: windows-11-arm
arch: arm64
build_cmd: pnpm run build:win:arm64
signing_identity: ''

steps:
Expand Down Expand Up @@ -72,10 +77,12 @@ jobs:
exit 1
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
echo "make_latest=$([ "$prerelease" = "true" ] && echo false || echo true)" >> "$GITHUB_OUTPUT"
{
echo "version=$version"
echo "tag=v$version"
echo "prerelease=$prerelease"
echo "make_latest=$([ "$prerelease" = "true" ] && echo false || echo true)"
} >> "$GITHUB_OUTPUT"

- name: Set package version
env:
Expand All @@ -84,21 +91,26 @@ jobs:
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); pkg.version=process.env.RELEASE_VERSION; fs.writeFileSync('package.json', JSON.stringify(pkg,null,2)+'\n');"

- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 9

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.12'

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
architecture: ${{ matrix.arch }}
cache: 'pnpm'

- name: Set up MSBuild
if: startsWith(matrix.os, 'windows')
uses: microsoft/setup-msbuild@v2

- name: Install plugin dependencies
run: |
cd internal-plugins/setting
Expand All @@ -107,6 +119,46 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build and verify Windows native API
if: startsWith(matrix.os, 'windows')
shell: pwsh
env:
NATIVE_API_COMMIT: bb2025c9dddeedfd0738b7632650b2e4e8147466
TARGET_ARCH: ${{ matrix.arch }}
run: |
if ((node -p process.arch) -ne $env:TARGET_ARCH) {
throw "Node architecture does not match the target: $(node -p process.arch)"
}

$nativeRoot = Join-Path $env:RUNNER_TEMP 'ztools-native-api'
git clone https://github.com/ZToolsCenter/ZTools-native-api.git $nativeRoot
git -C $nativeRoot checkout --detach $env:NATIVE_API_COMMIT

Push-Location $nativeRoot
try {
npm ci --ignore-scripts
npm run build
} finally {
Pop-Location
}

$binaryPath = Join-Path $nativeRoot 'build/Release/ztools_native.node'
if (-not (Test-Path $binaryPath)) {
throw "Native API build output was not found: $binaryPath"
}

# Read IMAGE_FILE_HEADER.Machine directly so packaging cannot hide a cross-arch binary.
$bytes = [System.IO.File]::ReadAllBytes($binaryPath)
$peOffset = [System.BitConverter]::ToInt32($bytes, 0x3c)
$machine = [System.BitConverter]::ToUInt16($bytes, $peOffset + 4)
$expectedMachine = if ($env:TARGET_ARCH -eq 'arm64') { 0xaa64 } else { 0x8664 }
if ($machine -ne $expectedMachine) {
throw ('Native API PE machine mismatch: expected 0x{0:x4}, got 0x{1:x4}' -f $expectedMachine, $machine)
}

Copy-Item $binaryPath 'resources/lib/win/ztools_native.node' -Force
Write-Host ('Verified Windows native API PE machine 0x{0:x4}' -f $machine)

- name: Prepare App Store Connect API key
if: startsWith(matrix.os, 'macos')
shell: bash
Expand Down Expand Up @@ -196,10 +248,12 @@ jobs:
exit 1
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
echo "make_latest=$([ "$prerelease" = "true" ] && echo false || echo true)" >> "$GITHUB_OUTPUT"
{
echo "version=$version"
echo "tag=v$version"
echo "prerelease=$prerelease"
echo "make_latest=$([ "$prerelease" = "true" ] && echo false || echo true)"
} >> "$GITHUB_OUTPUT"

- name: Set package version
env:
Expand All @@ -208,7 +262,7 @@ jobs:
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); pkg.version=process.env.RELEASE_VERSION; fs.writeFileSync('package.json', JSON.stringify(pkg,null,2)+'\n');"

- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 9

Expand All @@ -233,17 +287,24 @@ jobs:
name: artifact-macos-14-arm64
path: dist/mac-arm64

- name: Download Windows artifact
- name: Download Windows x64 artifact
uses: actions/download-artifact@v4
with:
name: artifact-windows-latest-x64
path: dist/windows
path: dist/windows-x64

- name: Download Windows ARM64 artifact
uses: actions/download-artifact@v4
with:
name: artifact-windows-11-arm-arm64
path: dist/windows-arm64

# 合并 macOS 双架构元数据,并规范化 Windows 与 macOS 的更新清单
# 合并 Windows 与 macOS 双架构元数据,生成各平台的统一更新清单
- name: Generate update info
env:
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
WINDOWS_UPDATE_METADATA: dist/windows/latest.yml
WINDOWS_X64_UPDATE_METADATA: dist/windows-x64/latest.yml
WINDOWS_ARM64_UPDATE_METADATA: dist/windows-arm64/latest.yml
MAC_X64_UPDATE_METADATA: dist/mac-x64/latest-mac.yml
MAC_ARM64_UPDATE_METADATA: dist/mac-arm64/latest-mac.yml
UPDATE_METADATA_OUTPUT_DIR: dist/release
Expand All @@ -261,9 +322,12 @@ jobs:
make_latest: ${{ steps.release_meta.outputs.make_latest }}
body_path: changelog.md
files: |
dist/windows/*.exe
dist/windows/*.zip
dist/windows/*.blockmap
dist/windows-x64/*.exe
dist/windows-x64/*.zip
dist/windows-x64/*.blockmap
dist/windows-arm64/*.exe
dist/windows-arm64/*.zip
dist/windows-arm64/*.blockmap
dist/mac-x64/*.dmg
dist/mac-x64/*.zip
dist/mac-x64/*.blockmap
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ _uTools 的开源实现 | 支持 macOS 和 Windows_
从 [Releases](https://github.com/ZToolsCenter/ZTools/releases) 页面下载最新版本:

- **macOS**: `ztools-x.x.x.dmg` 或 `ZTools-x.x.x-arm64-mac.zip`
- **Windows**: `ztools-x.x.x-setup.exe` 或 `ztools-x.x.x-win.zip`
- **Windows x64 / ARM64**: `ZTools-x.x.x-win-{arch}-setup.exe` 或 `ZTools-x.x.x-win-{arch}.zip`

#### 方式 2:从源码构建

Expand All @@ -112,7 +112,8 @@ pnpm dev

# 构建
pnpm build:mac # macOS
pnpm build:win # Windows
pnpm build:win:x64 # Windows x64(build:win 仍为 x64 别名)
pnpm build:win:arm64 # Windows ARM64
pnpm build:linux # Linux (当前主机架构)
pnpm build:linux:x64 # Linux (按 amd64/x64 架构打包)
pnpm build:linux:arm64 # Linux (按 arm64 架构打包)
Expand Down Expand Up @@ -217,7 +218,8 @@ pnpm lint # ESLint 检查
# 构建
pnpm build # 仅编译源码
pnpm build:mac # 打包 macOS 应用
pnpm build:win # 打包 Windows 应用
pnpm build:win:x64 # 打包 Windows x64 应用(build:win 仍为 x64 别名)
pnpm build:win:arm64 # 打包 Windows ARM64 应用
pnpm build:linux # 打包 Linux 应用 (当前主机架构)
pnpm build:linux:x64 # 打包 Linux 应用 (amd64/x64)
pnpm build:linux:arm64 # 打包 Linux 应用 (arm64)
Expand Down
8 changes: 5 additions & 3 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ English | [简体中文](./README.md)
Download the latest version from [Releases](https://github.com/ZToolsCenter/ZTools/releases):

- **macOS**: `ztools-x.x.x.dmg` or `ZTools-x.x.x-arm64-mac.zip`
- **Windows**: `ztools-x.x.x-setup.exe` or `ztools-x.x.x-win.zip`
- **Windows x64 / ARM64**: `ZTools-x.x.x-win-{arch}-setup.exe` or `ZTools-x.x.x-win-{arch}.zip`

#### Method 2: Build from Source

Expand All @@ -112,7 +112,8 @@ pnpm dev

# Build
pnpm build:mac # macOS
pnpm build:win # Windows
pnpm build:win:x64 # Windows x64 (build:win remains an x64 alias)
pnpm build:win:arm64 # Windows ARM64
pnpm build:linux # Linux (Default Arch)
pnpm build:linux:x64 # Linux (amd64/x64)
pnpm build:linux:arm64 # Linux (arm64)
Expand Down Expand Up @@ -205,7 +206,8 @@ pnpm lint # ESLint check
# Build
pnpm build # Compile source code only
pnpm build:mac # Package macOS app
pnpm build:win # Package Windows app
pnpm build:win:x64 # Package Windows x64 app (build:win remains an x64 alias)
pnpm build:win:arm64 # Package Windows ARM64 app
pnpm build:linux # Package Linux app (Default Arch)
pnpm build:linux:x64 # Package Linux app (amd64/x64)
pnpm build:linux:arm64 # Package Linux app (arm64)
Expand Down
8 changes: 2 additions & 6 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,8 @@ win:
executableName: ZTools
artifactName: ${name}-${version}-${os}-${arch}.${ext}
target:
- target: nsis
arch:
- x64
- target: zip
arch:
- x64
- nsis
- zip
nsis:
artifactName: ${name}-${version}-${os}-${arch}-setup.${ext}
include: build/installer.nsh
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"build": "pnpm typecheck && pnpm build:setting && electron-vite build",
"build:setting": "cd internal-plugins/setting && pnpm build",
"build:unpack": "pnpm build && electron-builder --dir",
"build:win": "cross-env ZTOOLS_TARGET_PLATFORM=win32 pnpm build && electron-builder --win --publish never",
"build:win": "pnpm build:win:x64",
"build:win:x64": "cross-env ZTOOLS_TARGET_PLATFORM=win32 pnpm build && electron-builder --win --x64 --publish never",
"build:win:arm64": "cross-env ZTOOLS_TARGET_PLATFORM=win32 pnpm build && electron-builder --win --arm64 --publish never",
"build:mac": "cross-env ZTOOLS_TARGET_PLATFORM=darwin pnpm build && electron-builder --mac --publish never",
"build:mac:x64": "cross-env ZTOOLS_TARGET_PLATFORM=darwin pnpm build && electron-builder --mac --x64 --publish never",
"build:mac:arm64": "cross-env ZTOOLS_TARGET_PLATFORM=darwin pnpm build && electron-builder --mac --arm64 --publish never",
Expand Down Expand Up @@ -67,7 +69,7 @@
"simple-plist": "^1.3.1",
"tar": "^7.5.9",
"turndown": "^7.2.2",
"uiohook-napi": "^1.5.4",
"uiohook-napi": "^1.5.5",
"uuid": "^13.0.0",
"vuedraggable": "^4.1.0",
"winreg": "^1.2.5",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 5 additions & 6 deletions scripts/sync-release-server.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readFileSync } from 'fs'
import path from 'path'
import { readUpdateMetadata } from './update-metadata.mjs'
import { readUpdateMetadata, selectWindowsInstaller } from './update-metadata.mjs'

/* eslint-disable @typescript-eslint/explicit-function-return-type */

Expand Down Expand Up @@ -107,10 +107,8 @@ async function main() {
process.env.MAC_UPDATE_METADATA || 'dist/release/latest-mac.yml'
)
const release = await fetchGitHubRelease(repository, tag, githubToken)
const windowsFile = (windowsMetadata.files || []).find((file) =>
String(file?.url || '').endsWith('-setup.exe')
)
if (!windowsFile) throw new Error('Windows 更新元数据缺少 NSIS 安装包')
const windowsX64Installer = selectWindowsInstaller(windowsMetadata, 'x64')
const windowsArm64Installer = selectWindowsInstaller(windowsMetadata, 'arm64')

const payload = {
providerReleaseId: release.id,
Expand All @@ -121,7 +119,8 @@ async function main() {
publishedAt: release.published_at,
prerelease: Boolean(release.prerelease),
artifacts: [
toArtifact(release, windowsFile, 'windows-x64-installer'),
toArtifact(release, windowsX64Installer, 'windows-x64-installer'),
toArtifact(release, windowsArm64Installer, 'windows-arm64-installer'),
toArtifact(release, selectMetadataFile(macMetadata, '-x64.zip'), 'macos-x64'),
toArtifact(release, selectMetadataFile(macMetadata, '-arm64.zip'), 'macos-arm64')
]
Expand Down
45 changes: 45 additions & 0 deletions scripts/update-metadata.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,51 @@ export function mergeMacUpdateMetadata(x64Metadata, arm64Metadata, releaseNotes)
}
}

/**
* 从单架构 Windows 元数据中选择标准 NSIS 安装包。
* @param {Record<string, any>} metadata 单架构 Windows 更新元数据。
* @param {'x64' | 'arm64'} arch 目标架构。
* @returns {Record<string, any>} 对应架构的 NSIS 安装包文件信息。
* @throws {Error} 找不到唯一且带 SHA-512 的架构安装包时抛出错误。
*/
export function selectWindowsInstaller(metadata, arch) {
const files = Array.isArray(metadata.files) ? metadata.files : []
const suffix = `-${arch}-setup.exe`
const matches = files.filter((file) => String(file?.url || '').endsWith(suffix))

if (matches.length !== 1) {
throw new Error(`Windows ${arch} 元数据必须包含且仅包含一个 ${suffix} 安装包`)
}
if (!matches[0].sha512) throw new Error(`Windows ${arch} 安装包缺少 SHA-512`)
return { ...matches[0] }
}

/**
* 合并两个架构的 Windows 元数据,供 electron-updater 按运行架构选择安装包。
* @param {Record<string, any>} x64Metadata Windows x64 更新元数据。
* @param {Record<string, any>} arm64Metadata Windows ARM64 更新元数据。
* @param {string} releaseNotes 发布说明。
* @returns {Record<string, any>} 合并后的 latest.yml 元数据。
* @throws {Error} 版本不一致或任一架构安装包无效时抛出错误。
*/
export function mergeWindowsUpdateMetadata(x64Metadata, arm64Metadata, releaseNotes) {
if (!x64Metadata.version || x64Metadata.version !== arm64Metadata.version) {
throw new Error('Windows x64 与 arm64 更新元数据版本不一致')
}

// 保持 x64 在前并保留顶层兼容字段,旧客户端仍会下载 x64 安装包。
const x64Installer = selectWindowsInstaller(x64Metadata, 'x64')
const arm64Installer = selectWindowsInstaller(arm64Metadata, 'arm64')

return {
...x64Metadata,
files: [x64Installer, arm64Installer],
path: x64Installer.url,
sha512: x64Installer.sha512,
releaseNotes
}
}

/**
* 为平台更新元数据补充统一发布说明。
* @param {Record<string, any>} metadata 原始更新元数据。
Expand Down
Loading