Pkl configuration
While YAML is our official configuration format, we want to support dynamic formats, and as such, have added support for Pkl. What is Pkl? If you haven't heard of Pkl yet, Pkl is a programmable configuration format by Apple. We like Pkl, as it meets the following requirements:
- Is easy to read and write.
- Is dynamic and programmable (loops, variables, etc).
- Has type-safety / built-in schema support.
- Has Rust serde integration.
The primary requirement that we are hoping to achieve is supporting a configuration format that is programmable. We want something that has native support for variables, loops, conditions, and more, so that you could curate and compose your configuration very easily. Hacking this functionality into YAML is a terrible user experience in our opinion!
Installing Pkl
Pkl utilizes a client-server architecture, which means that the pkl
binary must exist in the
environment for parsing and evaluating .pkl
files. Jump over to the
official documentation for instructions on how to install Pkl.
If you are using proto, you can install Pkl with the following commands.
proto plugin add pkl https://raw.githubusercontent.com/milesj/proto-plugins/refs/heads/master/pkl.toml
proto install pkl --pin
Using Pkl
To start using Pkl in moon, simply:
- Install Pkl and the VS Code extension
- Create configs with the
.pkl
extension instead of.yml
We highly suggest reading the Pkl language reference, the standard library, or looking at our example configurations when using Pkl.
Caveats and restrictions
Since this is an entirely new configuration format that is quite dynamic compared to YAML, there are some key differences to be aware of!
-
Only files are supported. Cannot use or extend from URLs.
-
Each
.pkl
file is evaluated in isolation (loops are processed, variables assigned, etc). This means that task inheritance and file merging cannot extend or infer this native functionality. -
default
is a special feature in Pkl and cannot be used as a setting name. This only applies totemplate.pkl
, but can be worked around by usingdefaultValue
instead.
variables {
["age"] {
type = "number"
prompt = "Age?"
defaultValue = 0
}
local
is also a reserved word in Pkl. It can be worked around by escaping it with backticks, or you can simply use thepreset
setting instead.
tasks {
["example"] {
`local` = true
# Or
preset = "server"
}
}
Example configs
.moon/workspace.pkl
projects {
globs = List("apps/*", "packages/*")
sources {
["root"] = "."
}
}
vcs {
defaultBranch = "master"
}
.moon/toolchain.pkl
node {
version = "20.15.0"
packageManager = "yarn"
yarn {
version = "4.3.1"
}
addEnginesConstraint = false
inferTasksFromScripts = false
}
moon.pkl
type = "application"
language = "typescript"
dependsOn = List("client", "ui")
tasks {
["build"] {
command = "docusaurus build"
deps = List("^:build")
outputs = List("build")
options {
interactive = true
retryCount = 3
}
}
["typecheck"] {
command = "tsc --build"
inputs = new Listing {
"@globs(sources)"
"@globs(tests)"
"tsconfig.json"
"/tsconfig.options.json"
}
}
}
Example functionality
Loops and conditionals
tasks {
for (_os in List("linux", "macos", "windows")) {
["build-\(_os)"] {
command = "cargo"
args = List(
"--target",
if (_os == "linux") "x86_64-unknown-linux-gnu"
else if (_os == "macos") "x86_64-apple-darwin"
else "i686-pc-windows-msvc",
"--verbose"
)
options {
os = _os
}
}
}
}
Local variables
local _sharedInputs = List("src/**/*")
tasks {
["test"] {
// ...
inputs = List("tests/**/*") + _sharedInputs
}
["lint"] {
// ...
inputs = List("**/*.graphql") + _sharedInputs
}
}