pkfire
带有 Bazel 风格增量缓存的类型化任务运行器,使用 Pkl 进行配置。
名称 pkfire 源自“Pkl 任务点火”:在 Pkl 中定义任务,
然后通过 pkf CLI 执行它们。
pkfire(CLI:pkf)用类型化、
可组合的 Pkl 模式取代了手写的 justfile。任务声明其输入、输出和
依赖项;pkf 构建 DAG 并仅执行其
操作键已更改的步骤。缓存的输出从
~/.cache/pkfire 下的内容寻址存储中恢复。
为什么选择 pkfire
pkfire 与你已经
常用的同类轻量级任务运行器竞争——make、just、npm
scripts、package.json "scripts"、Taskfile.yml。它们对于
少量单行 shell 命令都能正常工作。一旦项目包含以下内容,
痛点就会显现:
- 共享输入(“这 6 个
.go文件的 glob 模式 供三个不同的任务使用”),你一直在复制粘贴。 - 矩阵重复——四个几乎相同的配方用于
linux-amd64、linux-arm64、darwin-amd64、darwin-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-amd64、linux-arm64、darwin-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:
| Input | Default | Notes |
|---|---|---|
version | 操作引用,回退到最新 release | 接受 v0.5.0、0.4.0、v0(浮动主版本)或底层的 pkfire@0.14.2。通过 uses: mizchi/pkfire@v0.14.2 固定版本是推荐形式。 |
pkl-version | 0.32.1 | 设置为 none 以在仅需要 pkf 时跳过 Pkl 安装。 |
install-dir | ${{ runner.temp }}/pkfire-bin | 两个二进制文件均放置于此;该目录会被追加到 GITHUB_PATH。 |
cache-pkl | false | 设置为 true 以在运行之间缓存 ~/.pkl/cache。对于消费远程 Pkl 包的项目(package://pkg.pkl-lang.org/... 的 amends / import)很有用。 |
pkl-cache-key | PklProject.deps.json 的 pkl-<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-darwin 和 x86_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 输出相同的任务元数据,以及 kind 和
edges:
{
"tasks": [
{ "name": "build", "kind": "task", "deps": [], "cache": true },
{ "name": "ci", "kind": "aggregate", "deps": ["build"], "cache": true }
],
"edges": [
{ "from": "build", "to": "ci" }
]
}
任务 kind 是 task、aggregate、service 或 noop 之一。
图 edges 中的箭头从依赖项指向被依赖项。
测试受影响的工作流
当你首次编写 inputs、outputs 和 deps 时,将预期的
文件变更工作流固定在任务旁边:
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_SOCK、GPG_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 --watch;cmd读取$BUMP、$PORT、$WATCH。不同的值会缓存为 不同的条目 — 通常这是你想要的。 - 你希望可变的位置参数(
just *ARGS的形状)。 设置acceptsArgs = true并编写cmd = "node \"$@\""。调用者 传递pkf run task -- a b c。参数会折叠到操作键中, 因此命令包装器通常也会设置cache = false。 - 你希望将辅助任务从正常发现中隐藏。 设置
visibility = "internal"。pkf list和pkf 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成为默认值是有原因的。 一个静默吸收其名称后 任意内容的任务是一个拼写错误的隐患。仅针对命令包装器(script、test --grep=...等)选择启用。bool参数不会消耗下一个 token。--watch --port=80parses asWATCH=true PORT=80. Use--watch=false用于显式否定。(int、string、enum在未编写=时 确实 会 消耗下一个 token。)
指向模式
Taskfile.pkl 模式位于此仓库中。从下游项目中
选择适合你的选项:
| 选项 | amends 行 | 备注 |
|---|---|---|
| Pkl 包(推荐) | amends "package://pkg.pkl-lang.org/github.com/mizchi/pkfire/pkfire@0.14.2#/Taskfile.pkl" | 带版本控制、完整性校验,并由 Pkl 缓存。 |
| HTTPS,浮动 tip | amends "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.pkl(pkl 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/diagnostics | list --long、lint --json/--fix、doctor --json/--fix、内部任务、静默输出、严格 shell 标志 |
examples/split-import | 包含位于 tasks/ 下的任务片段、共享常量和类型化跨文件依赖的单一入口 Taskfile |
examples/dogfood | pkfire 构建自身:交叉编译矩阵 + 校验和 + 集成测试 |
examples/remote-cache-worker | 使用 R2 支持远程缓存协议的 Cloudflare Worker |
状态
| 阶段 | 范围 | 状态 |
|---|---|---|
| 0 | Pkl schema, pkl test 基线, CLI 骨架 | ✅ |
| 1 | 通过 pkl-go 加载 Taskfile.pkl, 构建 DAG, 串行执行 | ✅ |
| 2 | 并行执行并遵守 deps (每任务 IO 捕获) | ✅ |
| 3 | Action key (对 cmd / shell flags / env / inputs / tools / config 进行 BLAKE3 哈希) | ✅ |
| 4 | 本地 CAS, 命中/未命中, 输出恢复 | ✅ |
| 5 | 监听模式 (pkf run --watch) | ✅ |
| 6 | 远程缓存 (HTTP 后端 + 参考 Cloudflare Worker) | ✅ |
| 7 | Pkl 包发布 (pkg.pkl-lang.org/github.com/mizchi/pkfire/pkfire) | ✅ |
| 8 | GitHub Action (mizchi/pkfire@pkfire@<ver>) + 发布时的预构建二进制文件 | ✅ |
| 9 | pkf 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.