Script
Terrabuild custom extensions are implemented with FScript (.fss).
Configure a Scripted Extension
Local script (workspace-confined)
WORKSPACE
extension npm_ci {
script = "tools/extensions/npm-ci.fss"
}The script path is resolved from the workspace and must remain inside the workspace.
Remote script (HTTPS)
WORKSPACE
extension npm_ci {
script = "https://example.org/terrabuild/npm-ci.fss"
}Only HTTPS URLs are accepted.
Use extension actions
PROJECT
target build {
npm_ci install { args = "--frozen-lockfile" }
}Action names map to exported function names (or to the handler flagged dispatch).
Script Contract
Exported handlers must follow these rules:
- Use
export let. - First parameter must be
context. - Additional parameter names are matched exactly (case-sensitive) with target arguments.
- Omitted non-context arguments must be typed as
option<_>.
Example:
export let dispatch (context: { Command: string }) (args: string option) =
let command =
match args with
| Some value -> $"{context.Command} {value}"
| None -> context.Command
[{ Command = "npm"; Arguments = command; ErrorLevel = 0 }]Descriptor flags
Each script returns a descriptor map (function -> flags) at top-level.
Flags are discriminated union cases (not strings).
type ExportFlag =
| Dispatch
| Default
| Batchable
| Never
| Local
| External
| Remote
{
[nameof defaults] = [Default]
[nameof dispatch] = [Dispatch; Never]
[nameof build] = [Batchable; Remote]
}Supported flags:
DispatchDefaultBatchable- cacheability:
Never,Local,External,Remote
Extension Template
Copy/paste starter:
type BatchContext = {
Hash: string
TempDir: string
ProjectPaths: string list
}
type ActionContext = {
Debug: bool
CI: bool
Command: string
Hash: string
Directory: string
Batch: BatchContext option
}
type ProjectInfo = {
Id: string option
Outputs: string list
Dependencies: string list
}
type ShellOperation = {
Command: string
Arguments: string
ErrorLevel: int
}
type ShellOperations = ShellOperation list
type ExportFlag =
| Dispatch
| Default
| Batchable
| Never
| Local
| External
| Remote
let with_args args =
args |> Option.defaultValue ""
export let defaults (context: ActionContext) : ProjectInfo =
{ Id = None; Outputs = []; Dependencies = [] }
export let dispatch (context: ActionContext) (args: string option) : ShellOperations =
let command =
match with_args args with
| "" -> context.Command
| value -> $"{context.Command} {value}"
[{ Command = "your-tool"; Arguments = command; ErrorLevel = 0 }]
export let build (context: ActionContext) (configuration: string option) (args: string option) : ShellOperations =
let config = configuration |> Option.defaultValue "Debug"
let cmdArgs =
match with_args args with
| "" -> $"build --configuration {config}"
| value -> $"build --configuration {config} {value}"
[{ Command = "your-tool"; Arguments = cmdArgs; ErrorLevel = 0 }]
{
[nameof defaults] = [Default]
[nameof dispatch] = [Dispatch; Never]
[nameof build] = [Remote]
}Notes
- Context types are structural: handlers can request only required fields.
- Filesystem external functions are sandboxed to workspace scope and can be further denied with
workspace.deny. workspace.denyaccepts glob paths and defaults to[ ".git" ]when omitted.- Security bypass attempts (for example traversal through denied paths) are blocked by the runtime sandbox.
- For protocol details, see
src/Terrabuild/Scripts/EXTENSION-PROTOCOL.mdin the Terrabuild repository. - FScript repository: https://github.com/MagnusOpera/FScript
Last updated on