首页
/ 在 GitHub Actions 中一行打包网页:Pake Composite Action(`tw93/Pake@v3`)全解析

在 GitHub Actions 中一行打包网页:Pake Composite Action(`tw93/Pake@v3`)全解析

2026-09-04 16:34:33作者:郦嵘贵Just

Pake 是一个基于 Rust + Tauri 的命令行工具,能把任意网页打包成轻量级桌面应用。除了本地运行 CLI 之外,Pake 在仓库根目录内置了一个 Composite Actiondocs/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.jssrc-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 / nameurl 是必填的打包对象;name 会原样传给 CLI 的 --name。关于应用名在 Windows/macOS 保留空格与大小写、在 Linux 转为小写连字符的规则,见 CLI 使用文档
  • icon:Action 只在值非空时才追加 --icon 参数(见下文调用链)。留空时 Pake CLI 会自动抓取网站图标并转换为平台对应格式,完整行为参考 CLI 文档的 icon 章节
  • width / height:默认值 1200 / 780 与 Pake CLI 的默认窗口尺寸一致(action.ymldefault: "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=1action.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.ymlruns: 段(using: "composite",两个 composite step)逐步展开。

第 1 步:Setup Environment(自动搭建工具链)

对应 action.yml 的 "Setup Environment" 步骤,shell: bash,做了三件事:

  1. 安装 Node.js 依赖:直接 npm install
  2. 按需构建 Pake CLI:仅当 dist/cli.js 不存在时执行 npm run cli:build。该脚本定义在 package.jsonscripts 段("cli:build": "cross-env NODE_ENV=production rollup -c"),产物即 bin 声明的 CLI 入口 dist/cli.js
  3. 按需安装 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.tsMacBuilder.tsWinBuilder.ts,真正执行 Tauri 构建。

第 3 步:Package Output(查找并搬运产物)

构建完成后,脚本在 src-tauri/target 下查找产物(action.yml):

  1. find 文件类安装包:*.deb*.exe*.msi*.dmg,取第一个命中(head -1);
  2. 若未找到,回退查找 macOS 的 .app 目录(呼应上面 PAKE_CREATE_APP=1 的产物形态差异);
  3. 找到后 mv$INPUT_OUTPUT_DIR/<basename>,移动失败则退回 cp -r;写入前再次校验 PACKAGE_PATH 无换行符,防止污染 GITHUB_OUTPUT
  4. 成功则 printf 'package-path=%s\n' "$PACKAGE_PATH" >> "$GITHUB_OUTPUT" 并打印确认;找不到任何产物则打印 "No package found" 并以退出码 1 失败。

以上三条输出相关的断言(id: buildGITHUB_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.ymlaction.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_windowincognito--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 UsageAdvanced Usage(均有中文版本:cli-usage_CN.mdadvanced-usage_CN.md)。

小结

  • 用法:在(fork 的)Pake 仓库 workflow 里加一步 uses: tw93/Pake@v3,必填 url + name,可选 iconwidth(默认 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
登录后查看全文
热门项目推荐
相关项目推荐