在 GitHub Actions 中一行打包网页:Pake Composite Action(`tw93/Pake@v3`)全解析
Pake 是一个基于 Rust + Tauri 的命令行工具,能把任意网页打包成轻量级桌面应用。除了本地运行 CLI 之外,Pake 在仓库根目录内置了一个 Composite Action(docs/pake-action.md 的讲解对象),让你的仓库只需添加一个 uses: tw93/Pake@v3 步骤,就能在 CI 中自动完成环境搭建、构建并产出可分发的安装包。读完本文,你将掌握该 Action 的全部输入/输出参数、各平台产物形态、跨平台矩阵构建写法,以及它内部的调用链与安全设计(环境变量传参、临时文件、换行注入防护),并能将其直接落地到自己的 CI 流程中。
快速开始:一个步骤即可构建
最简单的用法是在 workflow 中添加一个 step:
- name: Build Pake App
uses: tw93/Pake@v3
with:
url: "https://example.com"
name: "MyApp"
一个完整的最小 workflow 如下(官方示例):
name: Build Web App
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: tw93/Pake@v3
with:
url: "https://weekly.tw93.fun"
name: "WeeklyApp"
需要注意的一个前提:该 Composite Action 的构建脚本依赖工作区里的 dist/cli.js 与 src-tauri 模板,因此它设计用于在 Pake 仓库的 fork(或包含 Pake 完整代码的仓库)中运行——这正是 内建工作流使用指南 建议"Fork 仓库后运行 workflow"的原因。如果你的仓库不含 Pake 源码,Action 里的 npm run cli:build(基于 rollup.config.js)将无法产出 CLI。
输入参数(Inputs)逐项说明
docs/pake-action.md 给出的参数表如下,定义源头是 action.yml 第 8–39 行的 inputs: 段:
| 参数 | 说明 | 必填 | 默认值 |
|---|---|---|---|
url |
要打包的目标 URL | ✅ | — |
name |
应用名称 | ✅ | — |
output-dir |
产物输出目录 | dist |
|
icon |
自定义应用图标(URL 或路径) | (未提供时自动抓取站点图标) | |
width |
窗口宽度 | 1200 |
|
height |
窗口高度 | 780 |
|
debug |
启用调试模式 | false |
几个结合实现与 CLI 文档的补充点:
url/name:url是必填的打包对象;name会原样传给 CLI 的--name。关于应用名在 Windows/macOS 保留空格与大小写、在 Linux 转为小写连字符的规则,见 CLI 使用文档。icon:Action 只在值非空时才追加--icon参数(见下文调用链)。留空时 Pake CLI 会自动抓取网站图标并转换为平台对应格式,完整行为参考 CLI 文档的 icon 章节。width/height:默认值1200/780与 Pake CLI 的默认窗口尺寸一致(action.yml 中default: "1200"、default: "780",与 cli-usage.md 中--width/--height的默认值相互印证)。output-dir:脚本会先mkdir -p该目录,再把构建产物移入(action.yml)。该参数还会经过换行符校验,防止通过注入换行写入额外的GITHUB_OUTPUT键值对(详见"安全设计"一节)。debug:仅在字符串严格等于"true"时追加--debug标志。它对应 CLI 的 debug 选项(构建调试版并输出更多日志),而非浏览器开发者工具开关。
输出(Outputs)
| 输出 | 说明 |
|---|---|
package-path |
生成安装包的路径 |
在 action.yml 中,package-path 通过 value: ${{ steps.build.outputs.package-path }} 从名为 build 的步骤透出。也就是说,后续步骤可以直接消费它:
- uses: tw93/Pake@v3
id: pake
- name: Show result
run: echo "Package is at: ${{ steps.pake.outputs.package-path }}"
官方示例:自定义图标与多平台矩阵构建
带自定义图标、指定窗口尺寸
- uses: tw93/Pake@v3
with:
url: "https://example.com"
name: "MyApp"
icon: "https://example.com/icon.png"
width: 1400
height: 900
多平台矩阵构建
利用 GitHub 的 matrix 策略,可以在一次 workflow 中并行构建多个平台:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: tw93/Pake@v3
with:
url: "https://example.com"
name: "CrossPlatformApp"
文档建议的矩阵写法就是"哪种 runner 产出哪个平台的包",各平台的产物形态如下:
| 平台 | Runner | 产物 |
|---|---|---|
| Linux | ubuntu-latest |
.deb 包 |
| macOS | macos-latest |
.app 与 .dmg 包 |
| Windows | windows-latest |
.exe 与 .msi 包 |
一个值得注意的实现细节:Action 在 macOS 上构建时导出了环境变量 PAKE_CREATE_APP=1(action.yml)。对照 MacBuilder.ts 的构造逻辑——当 process.env.PAKE_CREATE_APP === '1' 时,buildFormat 被强制设为 app(而非默认的 dmg),这解释了为什么 Action 的 find 查找逻辑会"先找文件类安装包、找不到再回退到 .app 目录":在 CI 的无头环境里,.app bundle 是默认产物形态。
How It Works:Action 内部执行流程
docs/pake-action.md 概述了三步流程,下面对照 action.yml 的 runs: 段(using: "composite",两个 composite step)逐步展开。
第 1 步:Setup Environment(自动搭建工具链)
对应 action.yml 的 "Setup Environment" 步骤,shell: bash,做了三件事:
- 安装 Node.js 依赖:直接
npm install。 - 按需构建 Pake CLI:仅当
dist/cli.js不存在时执行npm run cli:build。该脚本定义在 package.json 的scripts段("cli:build": "cross-env NODE_ENV=production rollup -c"),产物即bin声明的 CLI 入口dist/cli.js。 - 按需安装 Rust/Cargo:若
cargo不在 PATH 中,用curl --proto '=https' --tlsv1.2 -sSf下载 rustup 安装脚本并静默执行,然后把$HOME/.cargo/bin追加到$GITHUB_PATH供后续步骤使用。这里使用了mktemp生成唯一临时文件存放安装脚本,并以trap 'rm -f "$rustup_init"' EXIT保证退出时清理(action.yml)——这是对共享 runner 上固定路径(如/tmp/rustup-init.sh)竞争覆盖问题的修复。
第 2 步:Build Pake App(拼装并执行 CLI 命令)
对应 action.yml 的 "Build Pake App" 步骤(id: build)。
输入通过环境变量传递,而非行内插值。该步骤先声明 env: 块,把 7 个 input 映射为 INPUT_* 环境变量(action.yml):
env:
INPUT_URL: ${{ inputs.url }}
INPUT_NAME: ${{ inputs.name }}
INPUT_ICON: ${{ inputs.icon }}
INPUT_WIDTH: ${{ inputs.width }}
INPUT_HEIGHT: ${{ inputs.height }}
INPUT_DEBUG: ${{ inputs.debug }}
INPUT_OUTPUT_DIR: ${{ inputs.output-dir }}
这个设计有明确的测试保障:tests/unit/action-input-security.test.ts 会读取 action.yml 源码并断言——run: 脚本中不得出现 ${{ inputs.* }} 行内表达式(正则 /\$\{\{\s*inputs\./ 必须不匹配),且上述 7 条 env 映射必须逐一存在。其含义是:用户输入永远不会被拼进 shell 命令文本,而是作为环境变量由 shell 在运行时读取,从结构上杜绝了输入内容改写脚本的可能性。
参数拼装逻辑(action.yml):
- 先校验
INPUT_OUTPUT_DIR不含换行符(\n/\r),违规直接报错退出; ARGS=("$INPUT_URL")以 URL 位置参数开头,随后追加--name "$INPUT_NAME";icon仅在非空时追加--icon;--width、--height始终追加(未传时为 action 默认值);debug仅在等于"true"时追加--debug;mkdir -p输出目录并export PAKE_CREATE_APP=1;- 最后以
printf ' %q'打印完整命令(便于在 CI 日志中复核),再执行node dist/cli.js "${ARGS[@]}"。
也就是说,Action 并没有绕过 Pake CLI,而是把它完整包了一层:CLI 内部由 BuilderProvider.ts 按当前 OS 分发到 LinuxBuilder.ts、MacBuilder.ts 或 WinBuilder.ts,真正执行 Tauri 构建。
第 3 步:Package Output(查找并搬运产物)
构建完成后,脚本在 src-tauri/target 下查找产物(action.yml):
- 先
find文件类安装包:*.deb、*.exe、*.msi、*.dmg,取第一个命中(head -1); - 若未找到,回退查找 macOS 的
.app目录(呼应上面PAKE_CREATE_APP=1的产物形态差异); - 找到后
mv到$INPUT_OUTPUT_DIR/<basename>,移动失败则退回cp -r;写入前再次校验PACKAGE_PATH无换行符,防止污染GITHUB_OUTPUT; - 成功则
printf 'package-path=%s\n' "$PACKAGE_PATH" >> "$GITHUB_OUTPUT"并打印确认;找不到任何产物则打印 "No package found" 并以退出码 1 失败。
以上三条输出相关的断言(id: build、GITHUB_OUTPUT 写入格式、output 值映射)同样被 action-input-security.test.ts 固化,属于"文档承诺即代码契约"的验证方式。
安全设计:为什么输入处理要这么讲究
docs/pake-action.md 没有专门展开安全部分,但 action.yml 的实现里体现了几处对 CI 注入场景的防御,值得了解:
- 表达式与脚本分离:
run:脚本中不出现任何${{ inputs.* }},所有输入经env:传递(见上文),测试用例逐条断言该约束; - 换行注入防护:
GITHUB_OUTPUT采用key=value逐行追加格式,若output-dir或最终包路径中含换行符,攻击者可以追加任意键(例如覆盖steps.*.outputs或触发后续步骤执行)。脚本在写入前对两处路径做了$'\n'/$'\r'检查并直接失败退出(action.yml 与 action.yml); - 临时文件唯一化:rustup 安装脚本经
mktemp生成、trap清理,不再使用固定的/tmp/rustup-init.sh(测试显式断言源码中不再包含该路径); - 命令打印与执行分离:
printf ' %q'让日志中的命令与实际执行内容一一对应,便于审计参数是否被意外篡改。
与仓库内建 Workflow 的关系
Pake 项目自身维护了两套基于 GitHub Actions 的构建方式,不要混淆:
- 本文的主角——Composite Action(action.yml):供你自己的项目通过
uses: tw93/Pake@v3引用的通用打包步骤,参数即上文 Inputs 表。 - 项目内建 workflow:位于 .github/workflows/single-app.yaml 的 "Build Single Popular App",支持
workflow_dispatch表单触发,参数更丰富(new_window、incognito、--targets deb,appimage、macOS--targets universal --multi-arch、签名与公证 secrets 等),使用actions/upload-artifact上传产物、打 tag 时推送到 Release。其使用方式见 GitHub Actions Usage。
两者底层调用的是同一个 CLI(node dist/cli.js),差异只在于 workflow 层能传 --targets 等 Action 未暴露的 CLI 选项。更多 CLI 选项请参考 CLI Usage 与 Advanced Usage(均有中文版本:cli-usage_CN.md、advanced-usage_CN.md)。
小结
- 用法:在(fork 的)Pake 仓库 workflow 里加一步
uses: tw93/Pake@v3,必填url+name,可选icon、width(默认 1200)、height(默认 780)、output-dir(默认dist)、debug;输出package-path供后续步骤使用。 - 原理:composite action 自动完成
npm install→ 按需cli:build→ 按需装 Rust → 环境变量传参调用node dist/cli.js→ 在src-tauri/target中定位.deb/.exe/.msi/.dmg/.app并搬运到输出目录。 - 平台:Ubuntu runner 出
.deb,macOS runner 出.app/.dmg(CI 中默认.app形态),Windows runner 出.exe/.msi;矩阵策略可并行多平台。 - 可验证依据:参数定义见 action.yml,安全约束见 tests/unit/action-input-security.test.ts,macOS 产物形态见 bin/builders/MacBuilder.ts,文档主体见 docs/pake-action.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00