From 265cd1cd6de6cf361ca7b995a3b0daa09babf9e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E7=BA=AA?= <3049035704@qq.com> Date: Fri, 5 Jun 2026 23:13:30 +0800 Subject: [PATCH 1/2] Add macOS packaging workflow --- .github/workflows/build-app.yml | 76 +++++++++++++++++++++++++++++++++ README.md | 10 +++++ docs/mac-release.md | 61 ++++++++++++++++++++++++++ package.json | 8 +++- src/main/index.ts | 5 +++ src/preload/index.ts | 2 + src/renderer/src/App.tsx | 9 +--- src/renderer/src/vite-env.d.ts | 1 + 8 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/build-app.yml create mode 100644 docs/mac-release.md diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml new file mode 100644 index 0000000..9621169 --- /dev/null +++ b/.github/workflows/build-app.yml @@ -0,0 +1,76 @@ +name: Build Desktop App + +on: + push: + branches: + - main + tags: + - "v*" + paths: + - ".github/workflows/build-app.yml" + - "electron.vite.config.ts" + - "package.json" + - "package-lock.json" + - "tsconfig.json" + - "docs/icon.png" + - "src/**" + pull_request: + paths: + - ".github/workflows/build-app.yml" + - "electron.vite.config.ts" + - "package.json" + - "package-lock.json" + - "tsconfig.json" + - "docs/icon.png" + - "src/**" + workflow_dispatch: + +permissions: + contents: read + +jobs: + package: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: Windows x64 + os: windows-2025 + command: npm run dist:win + artifact: topspeed-builder-windows-x64 + - name: macOS arm64 + os: macos-15 + command: npm run dist:mac:arm64 + artifact: topspeed-builder-macos-arm64 + - name: macOS Intel + os: macos-15-intel + command: npm run dist:mac:x64 + artifact: topspeed-builder-macos-x64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Package app + run: ${{ matrix.command }} + env: + CSC_IDENTITY_AUTO_DISCOVERY: false + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: release/**/* + if-no-files-found: error + retention-days: 14 diff --git a/README.md b/README.md index f82f7aa..6d13a9a 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,14 @@ npm run dev # 开发模式 npm run typecheck # 类型检查 npm run build # 构建到 out/ npm run dist:win # 打包 Windows 安装包(输出到 release/) +npm run dist:mac:arm64 # 在 Apple Silicon Mac 上打包 DMG/ZIP +npm run dist:mac:x64 # 在 Intel Mac 上打包 DMG/ZIP ``` 一键安装包位于 `release/Topspeed Builder Setup 1.0.1.exe`。 +macOS 内测包位于 `release/Topspeed Builder-1.0.1-mac-arm64.dmg` 或 `release/Topspeed Builder-1.0.1-mac-x64.dmg`。 + +macOS 打包需要在 macOS 环境执行。CI 会通过 `.github/workflows/build-app.yml` 产出 Windows、macOS arm64 和 macOS x64 内测包;正式分发前需要 Apple Developer ID 签名和公证,流程见 [macOS Build and Release](./docs/mac-release.md)。 ## 智能生成接口配置 @@ -181,9 +186,14 @@ npm run dev # dev mode npm run typecheck # type checking npm run build # compile to out/ npm run dist:win # package Windows installer (output in release/) +npm run dist:mac:arm64 # package Apple Silicon DMG/ZIP on macOS +npm run dist:mac:x64 # package Intel Mac DMG/ZIP on macOS ``` One-click installer at `release/Topspeed Builder Setup 1.0.1.exe`. +macOS test artifacts are written to `release/Topspeed Builder-1.0.1-mac-arm64.dmg` or `release/Topspeed Builder-1.0.1-mac-x64.dmg`. + +macOS packaging must run on macOS. The CI workflow at `.github/workflows/build-app.yml` builds Windows, macOS arm64, and macOS x64 test artifacts. Public macOS distribution requires Apple Developer ID signing and notarization; see [macOS Build and Release](./docs/mac-release.md). ## AI API Configuration diff --git a/docs/mac-release.md b/docs/mac-release.md new file mode 100644 index 0000000..3bde5be --- /dev/null +++ b/docs/mac-release.md @@ -0,0 +1,61 @@ +# macOS Build and Release + +This project supports macOS builds through electron-builder. Build macOS artifacts on macOS runners because the app uses `sharp`, a native dependency with platform-specific binaries. + +## Internal Test Builds + +Apple Silicon: + +```bash +npm ci +npm run dist:mac:arm64 +``` + +Intel Mac: + +```bash +npm ci +npm run dist:mac:x64 +``` + +Artifacts are written to `release/`: + +- `Topspeed Builder--mac-arm64.dmg` +- `Topspeed Builder--mac-arm64.zip` +- `Topspeed Builder--mac-x64.dmg` +- `Topspeed Builder--mac-x64.zip` + +The GitHub Actions workflow at `.github/workflows/build-app.yml` produces unsigned Windows and macOS artifacts for CI validation and internal testing. + +## Smoke Test Checklist + +Run this checklist on both Apple Silicon and Intel builds before publishing: + +1. Launch the app from the DMG. +2. Create a project under `Documents/Topspeed Builder Projects`. +3. Import PNG/JPG/WebP reference images. +4. Switch provider to `local-draft` and generate an icon, a character sheet, and a tileset. +5. Use "show in folder" from an asset card. +6. Export with ZIP enabled and open the export directory. +7. Reopen the app and confirm the recent project list loads. + +## Signed Distribution + +Unsigned artifacts are suitable only for internal testing. Public macOS distribution should be signed with a Developer ID Application certificate and notarized by Apple. + +Required GitHub Actions secrets for signed builds: + +- `CSC_LINK`: Base64-encoded `.p12` certificate or a secure URL to it. +- `CSC_KEY_PASSWORD`: Password for the `.p12` certificate. +- `APPLE_ID`: Apple Developer account email. +- `APPLE_APP_SPECIFIC_PASSWORD`: App-specific password for notarization. +- `APPLE_TEAM_ID`: Apple Developer Team ID. + +Signed scripts: + +```bash +npm run dist:mac:signed:arm64 +npm run dist:mac:signed:x64 +``` + +After secrets are configured, run the signed scripts on macOS release runners and publish the notarized DMG/ZIP files. diff --git a/package.json b/package.json index 489908a..7c2b3ff 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,10 @@ "dist": "npm run build && electron-builder", "dist:win": "npm run build && electron-builder --win", "dist:mac": "npm run build && electron-builder --mac", + "dist:mac:arm64": "npm run build && electron-builder --mac --arm64", + "dist:mac:x64": "npm run build && electron-builder --mac --x64", + "dist:mac:signed:arm64": "npm run build && electron-builder --mac --arm64 -c.mac.notarize=true", + "dist:mac:signed:x64": "npm run build && electron-builder --mac --x64 -c.mac.notarize=true", "dist:linux": "npm run build && electron-builder --linux", "typecheck": "tsc --noEmit" }, @@ -47,7 +51,9 @@ }, "mac": { "target": ["dmg", "zip"], - "icon": "docs/icon.png" + "icon": "docs/icon.png", + "category": "public.app-category.graphics-design", + "artifactName": "${productName}-${version}-mac-${arch}.${ext}" }, "linux": { "target": ["AppImage", "deb"], diff --git a/src/main/index.ts b/src/main/index.ts index 8b44c1e..ac94ccf 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -103,6 +103,11 @@ function registerIpc(): void { return true; }); + handle("shell:showProjectItem", async (projectPath: string, filePath: string) => { + shell.showItemInFolder(resolveProjectPath(projectPath, filePath)); + return true; + }); + handle("shell:openPath", async (filePath: string) => { const result = await shell.openPath(filePath); if (result) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 4b536e8..49ab426 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -26,5 +26,7 @@ contextBridge.exposeInMainWorld("topspeedBuilder", { ipcRenderer.invoke("image:dataUrl", projectPath, filePath), deleteAsset: (projectPath: string, assetId: string) => ipcRenderer.invoke("project:deleteAsset", projectPath, assetId), showItemInFolder: (filePath: string) => ipcRenderer.invoke("shell:showItem", filePath), + showProjectItemInFolder: (projectPath: string, filePath: string) => + ipcRenderer.invoke("shell:showProjectItem", projectPath, filePath), openPath: (filePath: string) => ipcRenderer.invoke("shell:openPath", filePath) }); diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 701aaa7..d79256c 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1576,7 +1576,7 @@ function AssetCard(props: { onClick={() => { const file = props.asset.metadataPath ?? pngFile; if (file) { - void props.runTask(t("busy.locateFile"), () => unwrap(window.topspeedBuilder.showItemInFolder(resolveFile(props.project.path, file)))); + void props.runTask(t("busy.locateFile"), () => unwrap(window.topspeedBuilder.showProjectItemInFolder(props.project.path, file))); } }} > @@ -1864,13 +1864,6 @@ function gameTypeLabelT(t: (key: string) => string, gameType: string): string { return found ? t(selectOptionLabel(found)) : gameType; } -function resolveFile(projectPath: string, filePath: string): string { - if (/^[a-zA-Z]:[\\/]/.test(filePath) || filePath.startsWith("/")) { - return filePath; - } - return `${projectPath}\\${filePath.replace(/\//g, "\\")}`; -} - function formatQueueError(error: unknown): string { const message = error instanceof Error ? error.message : String(error); const normalized = message.replace(/\s+/g, " ").trim(); diff --git a/src/renderer/src/vite-env.d.ts b/src/renderer/src/vite-env.d.ts index a889fe0..ec7aaf0 100644 --- a/src/renderer/src/vite-env.d.ts +++ b/src/renderer/src/vite-env.d.ts @@ -34,6 +34,7 @@ declare global { getHistory(projectPath: string): Promise>; readImageDataUrl(projectPath: string, filePath: string): Promise>; showItemInFolder(filePath: string): Promise>; + showProjectItemInFolder(projectPath: string, filePath: string): Promise>; openPath(filePath: string): Promise>; }; } From 25b018e233da29bc1124f4b89d47c6b0b0ca8304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E7=BA=AA?= <3049035704@qq.com> Date: Sat, 6 Jun 2026 18:46:59 +0800 Subject: [PATCH 2/2] Document macOS source run workflow --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 6d13a9a..c25dd7e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,27 @@ macOS 内测包位于 `release/Topspeed Builder-1.0.1-mac-arm64.dmg` 或 `releas macOS 打包需要在 macOS 环境执行。CI 会通过 `.github/workflows/build-app.yml` 产出 Windows、macOS arm64 和 macOS x64 内测包;正式分发前需要 Apple Developer ID 签名和公证,流程见 [macOS Build and Release](./docs/mac-release.md)。 +### macOS 命令行运行 + +如果暂时没有 macOS 安装包,Mac 用户可以从源码启动: + +```bash +git clone https://github.com/voicepeak/topspeed-builder.git +cd topspeed-builder +git checkout macos +npm ci +npm run dev +``` + +也可以使用生产构建预览: + +```bash +npm run build +npm start +``` + +建议使用 Node.js 22。若安装原生依赖失败,先执行 `xcode-select --install` 安装 Xcode Command Line Tools。 + ## 智能生成接口配置 在设置页填写: @@ -195,6 +216,27 @@ macOS test artifacts are written to `release/Topspeed Builder-1.0.1-mac-arm64.dm macOS packaging must run on macOS. The CI workflow at `.github/workflows/build-app.yml` builds Windows, macOS arm64, and macOS x64 test artifacts. Public macOS distribution requires Apple Developer ID signing and notarization; see [macOS Build and Release](./docs/mac-release.md). +### macOS CLI Run + +If a macOS installer is not available yet, Mac users can run the app from source: + +```bash +git clone https://github.com/voicepeak/topspeed-builder.git +cd topspeed-builder +git checkout macos +npm ci +npm run dev +``` + +Production preview is also available: + +```bash +npm run build +npm start +``` + +Node.js 22 is recommended. If native dependency installation fails, install Xcode Command Line Tools with `xcode-select --install`. + ## AI API Configuration Configure in the Settings page: