ITADN
mizchi/pkfire
mizchi/pkfire · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

pkfire

Test Nix

带有 Bazel 风格增量缓存的类型化任务运行器,使用 Pkl 进行配置。

名称 pkfire 源自“Pkl 任务点火”:在 Pkl 中定义任务, 然后通过 pkf CLI 执行它们。

pkfire(CLI:pkf)用类型化、 可组合的 Pkl 模式取代了手写的 justfile。任务声明其输入、输出和 依赖项;pkf 构建 DAG 并仅执行其 操作键已更改的步骤。缓存的输出从 ~/.cache/pkfire 下的内容寻址存储中恢复。

为什么选择 pkfire

pkfire 与你已经 常用的同类轻量级任务运行器竞争——makejust、npm scripts、package.json "scripts"Taskfile.yml。它们对于 少量单行 shell 命令都能正常工作。一旦项目包含以下内容, 痛点就会显现:

  • 共享输入(“这 6 个 .go 文件的 glob 模式 供三个不同的任务使用”),你一直在复制粘贴。
  • 矩阵重复——四个几乎相同的配方用于 linux-amd64linux-arm64darwin-amd64darwin-arm64
  • 单仓库中的按包覆盖,其中每个包都有一个 build 和一个 test 步骤,仅路径和工具链不同。
  • 无法验证 运行器配置本身——任务名称中的拼写错误 只会在你运行该任务时、在 CI 中、在周五才失败。

这些工具是基于字符串的:每个任务都是 shell,每个值都是 文本,每个引用都是按名称。它们没有“此 标识符应解析为已存在的 Task”的概念。因此它们 会重复。

pkfire 在 Pkl 中描述了相同的任务, 这是一种具有模板继承 (amends)、按模块测试(pkl test)和普通函数的类型化配置语言。 重复的任务形状变成一行 local function testTask(p) 由 schema 为每个包或平台调用——参见 examples/monorepo/,其中 按包的测试/构建模板取代了一大堆近乎重复的 just 配方。在一处重命名任务会更新所有引用; 拼写错误的依赖项会在求值时失败,在 runner 启动之前。

在语言层之上,pkfire 添加了基于字符串的 runner 无法实现的部分:一个基于 inputs/cmd/env 的内容寻址缓存,一个 HTTP 远程缓存,以便 CI 和团队成员可以共享命中,以及一种 watch 模式,仅重新运行受影响的子图。

安装

Homebrew (Apple Silicon macOS)

此仓库同时充当自定义 Homebrew tap。注册其显式 URL 一次,然后安装 pkf;Homebrew 还会安装所需的 Pkl CLI:

brew tap mizchi/pkfire https://github.com/mizchi/pkfire
brew install mizchi/pkfire/pkf

稍后可使用 brew update && brew upgrade pkf 进行升级。由于 MoonBit 工具链未提供 x86_64 macOS 二进制文件,因此不支持 Intel macOS。

安装脚本

pkf 是一个自包含的 MoonBit 二进制文件。安装程序会检测您的 平台,下载对应的发布 tarball,校验其 校验和,并将 pkf 放置到 ~/.local/bin 中:

curl -fsSL https://raw.githubusercontent.com/mizchi/pkfire/main/install.sh | sh

通过环境变量或标志进行自定义:

# pin a version and choose the install dir
curl -fsSL https://raw.githubusercontent.com/mizchi/pkfire/main/install.sh \
  | sh -s -- --version 0.12.0 --dir /usr/local/bin
# env-var form: PKF_VERSION, PKF_INSTALL_DIR, PKF_NO_VERIFY

手动下载

或者从 最新发行版获取

# pick your target: linux-amd64 | linux-arm64 | darwin-arm64
target=linux-amd64
curl -fsSL -O "https://github.com/mizchi/pkfire/releases/latest/download/pkf-${target}.tar.gz"
tar -xzf "pkf-${target}.tar.gz"
install -m 0755 pkf /usr/local/bin/pkf

Intel macOS(darwin-amd64)和 Windows 不受支持。您还需要在 PATH 上安装 Pkl CLI(pkl);请从 pkl-lang.org 或通过您的 包管理器进行安装。(下方的 GitHub Action 和 Nix 包已为您捆绑了 pkl。)

GitHub Actions

一个仅用于设置的复合 action 位于仓库根目录:

# .github/workflows/ci.yml
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: mizchi/pkfire@v0.14.2       # or @v0 to track the latest 0.x
      - run: pkf run ci

该操作会下载匹配的 pkf 二进制文件和运行器(linux-amd64linux-arm64darwin-arm64)的 Pkl CLI,并将它们 添加到 PATH。不支持 Intel macOS(darwin-amd64)。运行完成后, 工作流的其余部分将直接调用 pkf —— 无需 go install,也无需 Pkl 引导程序。

为什么使用 @v0.5.0 而不是 @pkfire@0.14.2 当 ref 本身包含 @ 时,GitHub Actions 无法 解析 uses: <repo>@<ref> —— 整个工作流文件将因通用的 “workflow file issue” 错误而加载失败,且没有任何作业运行。Pkl 发布标签 是用于包 URI 的 pkfire@<ver>,因此 Release 工作流 还会在同一提交处发布 v<ver> 和一个浮动的 v<major> 标签。 从 uses: 中使用这些标签。为了获得最大的供应链 安全性,请直接固定到提交 SHA: uses: mizchi/pkfire@<40-char-sha> # v0.5.0

将操作 ref 固定到发布标签,以便操作代码、pkf 二进制文件和 Pkl 模式能够一起更新。为了在 CI 运行和开发者之间共享缓存命中,请配置远程缓存环境变量:

      - uses: mizchi/pkfire@v0.14.2
      - run: pkf run ci
        env:
          PKFIRE_REMOTE_CACHE: ${{ vars.PKFIRE_REMOTE_CACHE }}
          PKFIRE_REMOTE_TOKEN: ${{ secrets.PKFIRE_REMOTE_TOKEN }}

Inputs:

InputDefaultNotes
version操作引用,回退到最新 release接受 v0.5.00.4.0v0(浮动主版本)或底层的 pkfire@0.14.2。通过 uses: mizchi/pkfire@v0.14.2 固定版本是推荐形式。
pkl-version0.32.1设置为 none 以在仅需要 pkf 时跳过 Pkl 安装。
install-dir${{ runner.temp }}/pkfire-bin两个二进制文件均放置于此;该目录会被追加到 GITHUB_PATH
cache-pklfalse设置为 true 以在运行之间缓存 ~/.pkl/cache。对于消费远程 Pkl 包的项目(package://pkg.pkl-lang.org/...amends / import)很有用。
pkl-cache-keyPklProject.deps.jsonpkl-<hashFiles> + Taskfile.pkl仅当默认键在同一仓库中不相关的作业之间发生冲突时才覆盖。

Nix(无需工具链)

nix run github:mizchi/pkfire -- run hello       # one-shot
nix profile install github:mizchi/pkfire        # persistent

该 flake 安装预构建的 pkf 发布二进制文件(已针对 Linux 上的 Nix 闭包进行修补),并将其封装,使得捆绑的 Pkl CLI 自动位于 PATH 上——最终用户无需自行安装 MoonBit 工具链或 Pkl。每次向 main 推送以及每个 PR 的 Nix 工作流 都会验证该 flake 在 aarch64-darwinx86_64-linux 运行器上能否干净地构建;上方的徽章跟踪其状态。

nix develop 打开一个包含 MoonBit 工具链(moon)和 pkl 的 shell,用于开发 pkfire 本身。

快速入门

mkdir my-project && cd my-project
pkf init                # writes a starter Taskfile.pkl
pkf run hello           # smoke the generated task

pkf init 编写了一个 Taskfile,该 Taskfile 通过 HTTPS 获取 amends 模式,因此 您的项目无需克隆此仓库。

编写 Taskfile

amends "package://pkg.pkl-lang.org/github.com/mizchi/pkfire/pkfire@0.14.2#/Taskfile.pkl"

local build = new Task {
  name = "build"
  cmd = "moon build --target native --release"
  inputs { "src/**/*.mbt"; "src/**/moon.pkg"; "moon.mod" }
  outputs { "_build/native/release/build/main/main.exe" }
}

local test = new Task {
  name = "test"
  cmd = "moon test"
  inputs { "src/**/*.mbt"; "src/**/moon.pkg"; "moon.mod" }
  deps { build }            // direct Task reference, typo-checked by Pkl
}

tasks { build; test }

每个任务都是一个 Task 实例,具有唯一的 name。依赖项是 任务引用deps { build }),而非字符串——引用未定义的任务会在 Pkl 求值时 因名称解析错误而失败,早于运行器启动。在一处重命名任务 会自动更新所有引用。

仅聚合依赖项的任务可以省略 cmd

local ci = new Task {
  name = "ci"
  deps { build; test }
}

cmd<shell> <shellFlags...> <cmd> 运行。默认值为 shell = "bash"shellFlags = List("-c");对于严格模式或非 -c 运行时,请覆盖 shellFlags

local strict = new Task {
  name = "strict"
  shellFlags = List("-eu", "-o", "pipefail", "-c")
  cmd = "pkl format --check ."
}

local nodeSnippet = new Task {
  name = "node-snippet"
  shell = "node"
  shellFlags = List("-e")
  cmd = "console.log(process.argv.slice(2))"
}

对于标准输出/标准错误即为产物的包装任务,请设置 quiet = true 以抑制 pkfire 的每任务诊断行,同时 不隐藏命令自身的输出。

当未提供 -f 时,pkf 会从当前目录向上查找 最近的 Taskfile.pkl(与 git 用于 .git/ 的发现规则相同),因此以下任一方式效果相同:

cd services/api/internal && pkf run ci    # uses services/api/Taskfile.pkl
cd /repo/root && pkf run ci               # uses /repo/root/Taskfile.pkl
pkf list                       # show public tasks
pkf list --unsorted            # show tasks in Taskfile declaration order
pkf list --all                 # include internal tasks
pkf list --color=always        # force ANSI color (auto, always, never)
pkf list -v                    # add cmd preview and deps
pkf list --json                # machine-readable (for editor / CI tooling)
pkf run test                   # builds first, then tests; second run hits cache
pkf run -j 8 test              # cap parallelism at 8
pkf run --watch test           # re-run on input changes (Ctrl+C to stop)
pkf run --dry-run test         # preview: per-task hit/will-run/uncached status + cmd
pkf run --print-hash test      # print action keys, do not execute
pkf run --explain-cache test   # explain cache hit/miss/forced-run decisions
pkf run --no-cache test        # bypass cache lookup AND store for this run
pkf run --refresh test         # bypass cache lookup but DO re-store (re-baseline)
pkf up dev                     # start every service:true task in dev's subgraph
pkf up --watch dev             # same, plus restart-on-change
pkf graph                      # emit Graphviz DOT for the full DAG
pkf graph --format mermaid     # emit Mermaid flowchart (renders on GitHub)
pkf graph --json               # machine-readable graph (tasks + edges)
pkf graph --target test        # only the subgraph rooted at `test`
pkf doctor                     # diagnose pkf PATH, pkl/cache/remote/taskfile setup
pkf doctor --json              # emit structured setup checks
pkf doctor --fix --dry-run     # preview replacing stale pkf on PATH with this binary
pkf format                     # pkl format -w on the Taskfile's directory
pkf format --check pkl examples # exit 11 (CI-friendly) if anything is unformatted
pkf hooks install              # write .git/hooks/<event> shims for matching tasks
pkf hooks list                 # show which hook events are wired
pkf affected --since=origin/main test  # run only tasks affected by the PR diff
pkf affected --files src/main.go --explain --dry-run  # inspect file -> task matches
pkf affected --check           # run workflowTests declared in Taskfile.pkl
pkf run a b c                  # run multiple targets in one go (topological union)
pkf run                        # no args = the `default` task (errors if absent)
pkf run -- a b c               # forward args to the `default` task when it accepts args
pkf run --timing build         # also print per-task wall time at the end
pkf run 'test:*'               # glob over task names (also works on affected / clean)
pkf clean                      # rm declared outputs of every task; --dry-run to preview
pkf cache stats                # local CAS: entries, size, oldest/newest
pkf cache prune --older-than=7d  # drop stale entries (--dry-run to preview)
pkf cache rm <action-key>      # remove a specific entry (≥2-char prefix accepted)
pkf cache clear --yes          # nuke everything (scripting-safe with --yes)
pkf run --quiet build          # suppress per-task log lines (errors + summary still print)
pkf completion bash > ~/.bash_completion.d/pkf  # dynamic task-name completion
pkf completion zsh > "${fpath[1]}/_pkf"
pkf completion fish > ~/.config/fish/completions/pkf.fish
pkf run --keep-going lint test # don't stop on first failure (Bazel / make -k)
pkf list --long                # audit task visibility/cache/quiet/deps/io/shell flags
pkf explain build              # dump every input to the action key (cache-miss debug)
pkf explain --diff old/Taskfile.pkl build  # compare action-key inputs against another Taskfile
pkf run --profile=ci build     # tag the run; $PKF_PROFILE + cache splits per profile
pkf run --on-fail=shell build  # drop into $SHELL in the failed task's workdir on error
pkf run --remote-only build    # skip local cache, only consult remote (verify remote populated)
pkf affected --watch           # re-evaluate affected set on every file change
pkf graph --target build --depth=1   # show only direct deps (one hop)
pkf graph --format tree        # terminal-readable dependency tree (roots only when no target)
pkf graph --format tree --target test --depth=2  # tree with deps up to two hops
pkf lint                       # detect dead local tasks, cache footguns, and suspicious task definitions
pkf lint --json                # emit machine-readable findings for CI/editor tooling
pkf lint --fix                 # safely add cache = false for outputs-without-inputs findings
pkf migrate --to=0.5.0         # rewrite Taskfile.pkl's amends URI + verify
pkf pkl-cache warm             # pre-populate ~/.pkl/cache (CI prefetch step)
pkf <plugin> <args>            # exec `pkf-<plugin>` on PATH (git-style fallthrough)

pkf lint 还解决了输入 glob、声明的输出 glob 以及仓库本地的 PKFIRE_MBT_CACHE_DIR 之间的交集问题。它会为自环和跨任务循环报告具体的见证路径。pkf watch 在启动前运行相同的检查,并拒绝那些可能反复触发自身的配置。例如,inputs { "**/*" } 不得与 outputs 重叠,并且自定义缓存目录通常应位于仓库外部或内置的已排除的 .cache/ 目录下。

cmd 中,始终会注入三个环境变量,以便任务可以引用其自身的上下文,而无需硬编码路径:

  • PKF_TASK_NAME — 任务的 name
  • PKF_TASK_ROOT — 任务的 workdir 的绝对路径(当 workdir 为 null 时,则为 Taskfile 目录)。
  • PKF_WORKSPACE_ROOT — Taskfile 所在目录的绝对路径。

这些不是 action key 的一部分——它们是任务定义的常量,已通过 cmd / env / inputs 隐含在哈希中。

对于缓存调试,pkf run --explain-cache <task> 会打印每个任务的 action key、缓存决策、声明的输出、匹配到的输入文件数量,以及未匹配到任何文件的输入模式。当您需要完整的逐组件 action-key 转储时,请使用 pkf explain <task>

可视化 Taskfile 是一个单一的管道:

pkf graph | dot -Tsvg -o tasks.svg
pkf graph --format mermaid > tasks.mmd
pkf graph --format tree --target test

机器可读的自省

当工具需要任务清单时,使用 pkf list --json;当它还需要依赖边时,使用 pkf graph --json。这两个命令默认遵循可见性设置;传入 --all 以包含内部任务,传入 --unsorted 以保留 Taskfile 声明顺序。

pkf list --json 输出:

{
  "tasks": [
    {
      "name": "build",
      "description": "Compile the app",
      "visibility": "public",
      "cmd": "moon build --target native --release",
      "deps": [],
      "inputs": ["src/**/*.mbt", "src/**/moon.pkg", "moon.mod"],
      "outputs": ["_build/native/release/build/main/main.exe"],
      "cache": true,
      "workdir": "services/api",
      "service": false,
      "services": [],
      "acceptsArgs": false,
      "inheritEnv": true
    }
  ]
}

pkf graph --json 输出相同的任务元数据,以及 kindedges

{
  "tasks": [
    { "name": "build", "kind": "task", "deps": [], "cache": true },
    { "name": "ci", "kind": "aggregate", "deps": ["build"], "cache": true }
  ],
  "edges": [
    { "from": "build", "to": "ci" }
  ]
}

任务 kindtaskaggregateservicenoop 之一。 图 edges 中的箭头从依赖项指向被依赖项。

测试受影响的工作流

当你首次编写 inputsoutputsdeps 时,将预期的 文件变更工作流固定在任务旁边:

local build = new Task {
  name = "build"
  cmd = "moon build --target native --release"
  inputs { "src/**/*.mbt"; "moon.mod" }
  outputs { "_build/native/release/build/main/main.exe" }
}

local test = new Task {
  name = "test"
  cmd = "moon test"
  inputs { "src/**/*.mbt" }
  deps { build }
}

tasks { build; test }

workflowTests {
  new {
    name = "source edit rebuilds and retests"
    changed { "src/main.mbt" }
    direct { "build" }
    tasks { "build"; "test" }
  }
}

pkf affected --check 运行这些用例,但不执行任务 命令。对于临时调试,请使用 pkf affected --files src/main.go --explain --dry-run 查看哪个 输入模式匹配以及哪些任务将包含在运行计划中。 反向视图是 pkf explain test:它现在会打印声明的依赖项、 依赖方、输入模式、输出,以及可能使该任务受影响的 上游输入模式。

环境变量、参数和操作键

本节是让自动化代理陷入困境的部分——这些规则 一旦陈述出来看起来显而易见,但如果你 猜测,它们看起来却是反的。阅读一次,必要时 参考回来。

层顺序(后者胜出)

每个 cmd 都在一个由四层合并而成的环境中运行:

1. host env (os.Environ())             ← inherited from the shell that ran pkf
2. defaults.Env                        ← Taskfile-wide common values
3. task.Env                            ← per-task overrides
4. resolved params (uppercased name)   ← `--bump=patch` → $BUMP

此外,当 acceptsArgs = true 时,命令行中 -- 之后的任何内容都会作为 $1$2、...、"$@" 转发。

两个并不相同的契约

cmd 可见?是操作键的一部分?
主机环境变量(当 inheritEnv = true 时,默认)
主机环境变量(当 inheritEnv = false 时,仅允许列表:PATH HOME LANG ...部分
defaults.Env
task.Env
解析后的 params 值($NAME✓(当 cache = true 时)
来自 -- a b c 的尾部参数($@✓(当 cache = true 时)
task.Tools仅作为环境变量提示

“主机环境变量”行的不匹配是有意为之。cmd 应该能够使用 SSH_AUTH_SOCKGPG_AGENT_INFO、你的 LANG、你的编辑器——而不会在下次你 ssh-add 不同密钥时悄悄破坏缓存。只有模式声明的层才参与操作键。

何时使用什么

  • 你希望 cmd 看到主机环境变量。 默认状态。无需 做任何操作 — inheritEnv = true 已经透传了所有内容。
  • 你希望主机环境变量同时影响缓存。 显式地将其读取到 task.Env 中:
    env { ["NODE_ENV"] = read("env:NODE_ENV") }
    
    现在 cmd 可以看到 $NODE_ENV,并且对其进行的更改会使 缓存条目失效。主机环境变量仍然会透传用于其他所有内容; 这仅将一个值提升到哈希层。
  • 你希望构建具有隔离性(发布流水线、对可重复性 敏感的 CI)。按任务设置 inheritEnv = false。然后 cmd 只能看到 极小的允许列表以及你放入 env { ... } 的内容, 并且操作键完全描述了环境。
  • 你希望每次调用时都会变化的运行时输入(端口、 提升类型、监视标志)。声明 params { ... }
    params {
      new { name = "bump"; type = "enum"; choices { "patch"; "minor"; "major" }; default = "patch" }
      new { name = "port"; type = "int";  default = "3000" }
      new { name = "watch"; type = "bool"; default = "false" }
    }
    
    调用者传递 pkf run task --bump=minor --port=8080 --watchcmd 读取 $BUMP$PORT$WATCH。不同的值会缓存为 不同的条目 — 通常这是你想要的。
  • 你希望可变的位置参数just *ARGS 的形状)。 设置 acceptsArgs = true 并编写 cmd = "node \"$@\""。调用者 传递 pkf run task -- a b c。参数会折叠到操作键中, 因此命令包装器通常也会设置 cache = false
  • 你希望将辅助任务从正常发现中隐藏。 设置 visibility = "internal"pkf listpkf graph 默认 隐藏它,--all 显示它,并且 pkf run <name> 仍然可以 直接执行它。

让代理感到困惑的事情

  • read("env:X") 并不是在运行时读取主机环境变量的方式。 它是 Pkl 求值时的插值:在 pkf 评估 Taskfile 时的值 会被固化到渲染后的任务中。 当你希望该值影响 action key 时,这正是你想要的,但如果你只需要 cmd 看到该变量, 普通的继承就足够了——不要仅为人体工程学上的便利而为像 SSH_AUTH_SOCK 这样的环境变量编写 read("env:...")
  • cmd 内部的 $VAR 是 shell 展开,而不是 Pkl 插值。 请编写 cmd = "echo $HOME" —— pkfire 会将字面字符串传递给 bash,bash 会从合并后的环境中展开 $HOME。Pkl 的 \(...) 插值在模式求值时运行,并将一个常量固化到渲染后的任务中——偶尔有用,但对于环境变量来说很少是你想要的。
  • acceptsArgs = false 成为默认值是有原因的。 一个静默吸收其名称后 任意内容的任务是一个拼写错误的隐患。仅针对命令包装器(scripttest --grep=... 等)选择启用。
  • bool 参数不会消耗下一个 token。 --watch --port=80 parses as WATCH=true PORT=80. Use --watch=false 用于显式否定。(intstringenum 在未编写 =确实 会 消耗下一个 token。)

指向模式

Taskfile.pkl 模式位于此仓库中。从下游项目中 选择适合你的选项:

选项amends备注
Pkl 包(推荐)amends "package://pkg.pkl-lang.org/github.com/mizchi/pkfire/pkfire@0.14.2#/Taskfile.pkl"带版本控制、完整性校验,并由 Pkl 缓存。
HTTPS,浮动 tipamends "https://raw.githubusercontent.com/mizchi/pkfire/main/pkl/Taskfile.pkl"旧版 pkf init 所写入的内容。Pkl 会获取并缓存。
HTTPS,固定标签amends "https://raw.githubusercontent.com/mizchi/pkfire/pkfire@0.14.2/pkl/Taskfile.pkl"固定到某个发布标签,无包解析。
本地克隆amends "../pkfire/pkl/Taskfile.pkl"mizchi/pkfire 是同级检出时。

该包作为 GitHub 发布版本发布,其标签与 pkfire@<version> 匹配。pkg.pkl-lang.org 将上述 URI 重定向到 发布 zip 文件 — 参见 pkl/PklProject 了解元数据, 参见 .github/workflows/pkl-publish.yml 了解发布流程。

远程缓存

设置 PKFIRE_REMOTE_CACHE(以及可选的 PKFIRE_REMOTE_TOKEN)以指向 pkf 任何支持缓存协议的 HTTP 服务器 — 本地 CAS 将成为写通层,且从未构建过的团队成员 / CI 运行器 可以在其首次运行时从远程恢复工件。

export PKFIRE_REMOTE_CACHE=https://pkfire-cache.<account>.workers.dev
export PKFIRE_REMOTE_TOKEN=<auth token>
pkf run build    # hits local first → falls back to remote → falls back to running

参考后端是一个 60 行的 Cloudflare Worker,用于在 R2 中存储 blob,并运行基于每日 TTL 的 GC;参见 examples/remote-cache-worker/

协议摘要:

GET  /v1/cas/<hex64>   → 200 + tar.zst | 404
HEAD /v1/cas/<hex64>   → 200 | 404
PUT  /v1/cas/<hex64>   → 201 (or 200 if already present)
Authorization: Bearer <token>   (optional)

Skill

如果你借助 Claude Code 代理(或任何 消费 APM 风格技能 的类似工具)来编写 Pkl 任务,请将其指向 skills/pkfire/SKILL.md。它记录了 模式、类型化deps模型、缓存语义以及常见 陷阱,此外在 skills/pkfire/assets/recipes/ 下还提供了 构建/测试、拆分/导入、服务、钩子、诊断和缓存 工作流的复制粘贴配方。

Used by

基于该模式或该操作构建的 实际使用者。 请提交 PR 以添加你的项目。

项目它提供的内容
kawaz/pkf-tasks作为 Pkl 包发布的共享 Pkl 任务模块:vcs/auto.pkl(通过抽象模块 + extends 实现 jj/git 运行时分发)、docs/translations.pkl(翻译对完整性)、lint/pkl.pklpkl format -w)。skills/pkfire/SKILL.md 中记录的库作者模式的实际示例。

Examples

路径展示内容
examples/basic最小化的 Taskfile(一个 hello,一个 build,一个 test
examples/node使用内置 node:test 运行器的 Node 项目;零开发依赖
examples/rust通过 cargo 驱动的单二进制 Rust crate(fmt + clippy + test + build)
examples/monorepo使用 Package 模板为每个包生成一个 Task 的 pnpm 工作区
examples/diagnosticslist --longlint --json/--fixdoctor --json/--fix、内部任务、静默输出、严格 shell 标志
examples/split-import包含位于 tasks/ 下的任务片段、共享常量和类型化跨文件依赖的单一入口 Taskfile
examples/dogfoodpkfire 构建自身:交叉编译矩阵 + 校验和 + 集成测试
examples/remote-cache-worker使用 R2 支持远程缓存协议的 Cloudflare Worker

状态

阶段范围状态
0Pkl schema, pkl test 基线, CLI 骨架
1通过 pkl-go 加载 Taskfile.pkl, 构建 DAG, 串行执行
2并行执行并遵守 deps (每任务 IO 捕获)
3Action key (对 cmd / shell flags / env / inputs / tools / config 进行 BLAKE3 哈希)
4本地 CAS, 命中/未命中, 输出恢复
5监听模式 (pkf run --watch)
6远程缓存 (HTTP 后端 + 参考 Cloudflare Worker)
7Pkl 包发布 (pkg.pkl-lang.org/github.com/mizchi/pkfire/pkfire)
8GitHub Action (mizchi/pkfire@pkfire@<ver>) + 发布时的预构建二进制文件
9pkf up: 长时运行服务 (service = true) 及进程组清理和监听驱动的重启
10在 body 任务上执行 services { ... }: pkf run e2e 启动实时服务器, 运行测试, 释放所有资源
11就绪探针 (readyPort / readyCmd): 复用已运行的服务并基于真实就绪状态门控依赖项
12环境变量继承默认值 + 可变尾部参数 (acceptsArgs) + 类型化命名参数 (params 支持 string/enum/int/bool) + 任务名称中的 /

开发

pkfire 自身使用自身: 仓库自身的 Taskfile.pkl 声明了 维护任务, 且构建 / 集成门控位于 examples/dogfood/Taskfile.pkl 中。pkf 本身是一个 MoonBit 程序 根目录位于仓库根目录 (moon.mod + src/); 使用 MoonBit 工具链构建它, 然后使用新构建的二进制文件驱动其余部分。

moon build src/cmd/pkf --target native --release
BIN=_build/native/release/build/mizchi/pkf/src/cmd/pkf/pkf.exe

"$BIN" list                                      # see all maintenance tasks
"$BIN" run preflight                             # moon check/test + pkl-test + examples + version + format
"$BIN" run conformance                           # contract harness: candidate vs frozen goldens (43/43)
"$BIN" run fmt                                    # pkl format -w on Taskfile.pkl, pkl/, examples/, skills/
"$BIN" run fmt:check                             # formatting check without writing
"$BIN" run -f examples/dogfood/Taskfile.pkl ci   # full build + integration gate

要发布 Pkl 软件包版本:

# 1. Bump README + skills + recipes + PklProject. Examples are NOT
#    touched here — they pin to a *published* URL and would 404 on
#    `pkl eval` until the release workflow finishes.
pkf run bump --to=<new-version>
git commit -am "release: pkfire@<new-version>"

# 2. Tag locally and push. Release + v-tags workflows fire.
# The Release workflow extracts the body for the GitHub release page
# from CHANGELOG.md's `## [<new-version>]` section automatically —
# update that section BEFORE this step so the published notes match.
pkf run tag
git push origin main "pkfire@<new-version>"

# 3. After the publish workflow uploads the package, bump examples
#    in a follow-up commit.
perl -i -pe 's/pkfire\@<old>/pkfire\@<new-version>/g' \
  examples/basic/Taskfile.pkl examples/node/Taskfile.pkl \
  examples/rust/Taskfile.pkl examples/monorepo/Taskfile.pkl \
  examples/diagnostics/Taskfile.pkl examples/split-import/Taskfile.pkl \
  examples/split-import/tasks/*.pkl
git commit -am "examples: bump amends URI to pkfire@<new-version>"
git push

pkf run check-version(封装 scripts/check-version-consistency.sh)涵盖了 README + skills + recipes 中的在途 schema 版本。出于上述发布顺序的原因,示例被 排除在外。

License

MIT — 参见 LICENSE.