前言
前段时间,笔者有个桌面端产品进入了快速迭代期,更新频率突然高了起来。
每次发版,我都得当"人肉流水线":
- 先在 Windows 电脑上跑一遍构建,打个 .exe 包。
- 然后再滚到 Mac 电脑上,再跑一遍,打个.dmg 包。
- 最后再吭哧吭哧手动传到服务器。
频繁更新几次后,我逐渐感到不对劲了:"都什么年代了,还在这儿玩'纯手工'?"
作为一个有追求的开发者,我意识到这样下去不行,至少 也得引入自动化构建, 不然每次构建十几二十分钟就浪费了。
我第一个想到的,就是 GitHub Actions。
为什么是 GitHub Actions ?
众所周知,GitHub 是赛博大善人。
在任何仓库内都提供了强大的自动化构建功能,它能够直接提供多种系统的虚拟机作为运行环境,而且笔者的项目代码正好托管在 GitHub 上,这简直是最佳选择!

更重要的是,这个功能对公开和私有仓库都非常友好:对公开仓库是免费的,私有仓库也配有 2000 分钟的免费时长限额(帐号共享)。

它直接提供了三种系统的虚拟机:Ubuntu、windows、macOS。
这非常适合我的需求(Windows 和 macOS 打包)。

别急着抄,先搞懂这 3 个"灵魂"配置
光会复制粘贴 AI 代码是不够的,你炼丹不得知道丹方的作用吗?代码也是,你最好知道每个重要配置项是什么作用的。
重点 1:strategy: matrix (并行构建)
YML
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
这是节省 Actions 资源的重要配置,因为 GitHub 是按虚拟机运行时间来计费的。
matrix: { os: [...] }:这是官方提供的"矩阵构建"。GitHub 看到这个,会同时启动两台虚拟机(一台 Mac,一台 Win),并行跑下面的所有 steps。
fail-fast: false:"防翻车"配置。 默认是 true,意思是一旦 Mac 挂了,Windows 也会被"连坐"干掉。改成 false 后,Mac 翻车了,Windows 照样跑它的,互不干扰。
重点 2:actions/cache@v4 (提速核心)
YML
- name: Cache npm and node_modules (macOS)
if: matrix.os == 'macos-latest'
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json', 'bun.lock', 'bun.lockb') }}
restore-keys: |
${{ runner.os }}-npm-
GitHub 虚拟机每次运行 Action 都是全新的,npm install 能慢死人。 actions/cache@v4 就是官方提供的缓存包,能把 node_modules 和 npm 缓存(~/.npm)打包存起来。
key 是"钥匙",用 lock 文件 的哈希值生成。
下次跑,如果 key 就一样,直接把缓存解压出来,npm ci 就能直接读缓存了 ⚡
重点 3:Upload Artifact (分包上传)
YML
- name: Upload macOS ARM64 artifact
if: matrix.os == 'macos-latest'
uses: actions/upload-artifact@v4
with:
name: macos-arm64-dmg
path: dist/electron/*-arm64.dmg
这是 Electron 项目的刚需。Mac 现在必须分 arm64(M 芯片)和 x64(Intel)。 我们用 path 通配符 *-arm64.dmg 来精确匹配,把它们分成不同的产物(Artifacts)上传,方便单独下载。
放一下笔者目前在用的配置
说实话,这玩意儿的配置项又多又杂。AI 写的也不一定好使,都得微调。(不过如果你对配置项很熟,可以在第一次 prompt 的时候就描述清楚关键词,这样后面改的工作量就少些)
笔者这里直接把我在用的 YAML 贴出来。这套配置改过几版,目前基本能用,**集成了"并行构建"、"防翻车"、"缓存提速"、"分包上传"**等玩意儿。
yml
# 1. 'name': 这个 Action 在 GitHub UI 上显示的名字
name: 跨平台构建(macOS + Windows)
# 2. 'on': 触发这个 Action 的事件
on:
# 2.0 自动触发:当 package.json 或任何锁文件变更时,在 main 分支上触发
push:
branches:
- master # 确保这是你要构建代码的分支
paths:
- 'package.json'
- 'bun.lock'
# 2.1 'workflow_dispatch': 允许你"手动"在 GitHub 网页上点按钮触发
workflow_dispatch:
# 2.1.1 'inputs': 定义手动触发时可以输入的参数
inputs:
# 2.1.1.1 'build_message': 定义一个叫 "build_message" 的输入框
build_message:
# 'description': 输入框旁边的提示文字
description: "构建说明(可选)"
# 'required': 是否必须填写 (false = 不必须)
required: false
# 'default': 留空时的默认值
default: "手动触发跨平台构建"
# 3. 'jobs': 定义这个 Action 要跑的所有"任务" (可以有多个 job)
jobs:
# 3.1 'build': 定义一个叫 "build" 的 job
build:
# 3.2 'strategy': 定义这个 job 的"策略" (比如并行、矩阵)
strategy:
# 3.2.1 'fail-fast': 关键配置!false = "防翻车"
# 意思是,一个平台(Mac)挂了,另一个(Win)继续跑,别"连坐"
fail-fast: false
# 3.2.2 'matrix': "矩阵",核心。让 job 并行跑多次
matrix:
# 3.2.2.1 'os': 定义一个叫 'os' 的变量,它有两个值
os: [macos-latest, windows-latest] # 这会导致 job 跑两次
# 3.3 'runs-on': 指定 job 跑在哪台虚拟机上
# '${{ matrix.os }}' 会动态读取矩阵里的值 (第一次是 macos, 第二次是 windows)
runs-on: ${{ matrix.os }}
# 3.4 'steps': 这个 job 要执行的"步骤" (按顺序执行)
steps:
# 3.4.1 'name': 步骤 1: "拉取代码" (UI上显示的名字)
- name: Checkout code
# 'uses': 使用一个"现成的" Action 插件 (来自市场)
uses: actions/checkout@v4 # v4 是版本号
# 3.4.2 'name': 步骤 2: "安装 Node.js 环境"
- name: Setup Node.js
# 'uses': 使用安装 Node.js 的插件
uses: actions/setup-node@v4
# 'with': 传给这个插件的参数
with:
# 'node-version': 指定 Node.js 的版本号 (锁死版本是好习惯)
node-version: "20"
# --- 缓存 node_modules (注释行,方便阅读) ---
# 3.4.3 'name': 步骤 3: "缓存 (Mac)" (提速核心)
- name: Cache npm and node_modules (macOS)
# 'if': 条件判断,只在 'os' 变量是 'macos-latest' 时才跑这一步
if: matrix.os == 'macos-latest'
# 'uses': 使用缓存插件
uses: actions/cache@v4
with:
# 'path': 要缓存的"路径" (Mac的npm缓存在 ~/.npm)
path: |
~/.npm
node_modules
# 'key': 缓存的"钥匙" (用系统名+lock文件哈希值)
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# 'restore-keys': "备用钥匙",key 没命中时,会尝试捞最近的缓存
restore-keys: |
${{ runner.os }}-npm-
# 3.4.4 'name': 步骤 4: "缓存 (Windows)"
- name: Cache npm and node_modules (Windows)
# 'if': 只在 Windows 虚拟机上跑
if: matrix.os == 'windows-latest'
uses: actions/cache@v4
with:
# 'path': Windows 的 npm 缓存路径不一样,得分开写
path: |
~\AppData\Roaming\npm-cache
node_modules
# 'key': 钥匙 (同上)
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# 'restore-keys': 备用钥匙 (同上)
restore-keys: |
${{ runner.os }}-npm-
# 3.4.5 'name': 步骤 5: "显示构建信息" (打日志,方便追溯)
- name: Show build info
# 'run': 自己跑 shell 命令
run: |
echo "🚀 触发平台 - ${{ matrix.os }}"
echo "📝 构建说明: ${{ github.event.inputs.build_message }}" # 读取手动输入的 'build_message'
# 'shell': 指定用哪个 shell 跑 (bash 通用性好)
shell: bash
# 3.4.6 'name': 步骤 6: "创建 .env 文件" (如果构建需要)
- name: Create .env file (if needed)
run: echo "NODE_ENV=production" > .env
shell: bash
# --- 使用 npm ci (注释行) ---
# 3.4.7 'name': 步骤 7: "安装依赖" (专业姿势)
- name: Install dependencies (npm)
run: |
# 'if': 检查 lock 文件在不在
if [ -f "package-lock.json" ]; then
# 'npm ci': 严格按 lock 文件安装 (CI环境首选,防灵异事件)
npm ci --include=dev # '--include=dev' 顺便装 devDependencies
else
# 'npm install': 备用方案
npm install
fi
shell: bash
# 3.4.8 'name': 步骤 8: "执行构建"
- name: Build application
# 'run': 跑 package.json 里的 "build:ci" 脚本
run: npm run build:ci
# 'env': 注入"环境变量"
env:
NODE_ENV: production # 告诉脚本,这是生产环境
CI: true # 告诉脚本,这是在 CI 里跑的
# --- 检查构建产物 (注释行) ---
# 3.4.9 'name': 步骤 9: "检查产物 (Mac)" (Debug神器)
- name: List build output and check sizes (macOS)
if: matrix.os == 'macos-latest'
run: |
# 'ls': 看看 'dist/electron' 目录里有啥 (|| echo... 是为了防止目录不存在时报错)
echo "=== Build output (dist/electron) ==="
ls -la dist/electron/ || echo "dist/electron/ not found"
# 'find': 找找有没有超过 500MB 的"胖"文件 (防止把不该打的打进去了)
find dist/ -type f -size +500M -exec ls -lh {} \; 2>/dev/null || echo "No oversized files found"
shell: bash
# 3.4.10 'name': 步骤 10: "检查产物 (Windows)"
- name: List build output and check sizes (Windows)
if: matrix.os == 'windows-latest'
run: |
echo "=== Build output (dist/electron) ==="
ls -la dist/electron/ || echo "dist/electron/ not found"
find dist/ -type f -size +500M -exec ls -lh {} \; 2>/dev/null || echo "No oversized files found"
shell: bash
# --- 分包上传 (注释行) ---
# 3.4.11 'name': 步骤 11: "上传 Mac ARM64 包" (Electron 刚需)
- name: Upload macOS ARM64 artifact
if: matrix.os == 'macos-latest'
# 'uses': 使用"上传产物"插件
uses: actions/upload-artifact@v4
with:
# 'name': 上传后,在 UI 上显示的名字 (M 芯片)
name: macos-arm64-dmg
# 'path': 要上传的文件的路径 (用通配符 *)
path: dist/electron/*-arm64.dmg
# 'retention-days': 产物保留 7 天 (别占人家便宜太久)
retention-days: 7
# 3.4.12 'name': 步骤 12: "上传 Mac Intel 包"
- name: Upload macOS Intel artifact
if: matrix.os == 'macos-latest'
uses: actions/upload-artifact@v4
with:
# 'name': Intel 芯片的包
name: macos-intel-dmg
path: dist/electron/*-x64.dmg
retention-days: 7
# 3.4.13 'name': 步骤 13: "上传 Windows 包"
- name: Upload Windows artifacts
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
# 'name': Windows 的安装包
name: windows-setup
path: |
dist/electron/*.exe
dist/electron/*.exe.blockmap # blockmap 是增量更新用的,顺便传
retention-days: 7
也可以看官方的快速启动教程
最后
从"人肉流水线"到"一键触发",其实就是一份 YAML 文件的距离。
对于有频繁构建烦恼、又不想掏钱买高额虚拟机的开发者来说,GitHub Actions 绝对是"赛博大善人"。
你可能会说,这 YAML 一百多行,谁记得住?
你不需要记住。
在 AI 时代,你只需要知道**"我需要一个带缓存、能防翻车、可以并行跑 Mac 和 Win 的 CI"**,然后把这个需求丢给 AI,让它给你出第一版配置。
我们的工作,已经从"埋头写"变成了"Review(审查)"。 理解上面那几个"灵魂"配置项,你就能轻松审查 AI 给你的 YAML 到底靠不靠谱。
快去试试吧,别再浪费时间"手动打包"啦!