Skip to content

Tamp Turbo

Scott Singleton edited this page May 15, 2026 · 3 revisions

Tamp.Turbo

Wrapper for Turborepo 2 — the monorepo task runner. HoldFast's frontend uses it for filtered builds and Dockerfile-staging via turbo prune --docker.

using Tamp.Turbo.V2;

Full reference: https://github.com/tamp-build/tamp-turbo.

Verbs

Turbo.Run(tool, s =>)             // turbo run <tasks…>
Turbo.Prune(tool, s =>)           // turbo prune (with --docker for Dockerfile staging)
Turbo.Ls(tool, s =>)              // experimental in 2.x
Turbo.Info(tool, s =>)            // debug context dump
Turbo.Daemon(tool, s =>)          // status / start / stop / restart / logs / clean
Turbo.Login / Logout / Link / Unlink (tool, s =>)
Turbo.Raw(tool,)                  // escape hatch

Common knobs (apply to every verb) live on TurboSettingsBase: Cwd, NoColor, Color, Ui, Verbosity, Api, Team, Login, Token (typed as Secret for remote cache auth), SkipInfer, NoUpdateNotifier.

CI behaviour to know about

packageManager field is required. Turbo 2 refuses to load a workspace whose root package.json lacks a "packageManager" entry. If the wrapper's integration tests pass against your fixture but the wrapper's own integration tests fail, that's almost always the missing field. Not the wrapper's job to add it.

prune --docker needs a real lockfile. Without one Turbo errors with Cannot prune without parsed lockfile. The integration tests run npm install --package-lock-only in fixture setup to satisfy this; production builds will already have a lockfile.

Remote cache token. Token is typed as Secret and joins the redaction table. Anything that subsequently logs the token gets scrubbed.

Quick example — HoldFast frontend build:fast

// Workspace-local turbo (the standard Turborepo deployment) — needs Tamp.Core ≥ 1.0.8.
// Bin doesn't exist until yarn install runs; consumers MUST DependsOn(YarnInstall).
[FromNodeModules("turbo")] readonly Tool TurboTool = null!;

// For globally-installed turbo (brew install turbo / npm i -g turbo), use [FromPath] instead.
// [FromPath("turbo")] readonly Tool TurboTool = null!;

[Secret("Turbo remote cache token", EnvironmentVariable = "TURBO_TOKEN")]
readonly Secret? TurboToken = null;

Target FrontendBuild => _ => _
    .DependsOn(nameof(YarnInstall))
    .Executes(() => Turbo.Run(TurboTool, s => s
        .AddTask("build:fast")
        .AddFilter("@holdfast-io/frontend...")
        .SetConcurrency(4)
        .SetContinue()
        .SetToken(TurboToken)
        .SetWorkingDirectory(RootDirectory / "frontend")));

Target FrontendDockerStaging => _ => _
    .DependsOn(nameof(YarnInstall))
    .Executes(() => Turbo.Prune(TurboTool, s => s
        .AddScope("@holdfast-io/frontend")
        .SetDocker()
        .SetOutDir("pruned")
        .SetWorkingDirectory(RootDirectory / "frontend")));

Multi-value flags are collection-shaped. Use AddTask / AddTasks / AddFilter / AddScope (NOT SetTask / SetFilter). Call them once per value; the wrapper joins them into the wire-level flag list. The "Set" naming is reserved for single-value knobs (SetConcurrency, SetToken, etc.).

See also

Settings authoring style

The example(s) above use the fluent Set*-chain shape. Every wrapper verb on this page also accepts a new XxxSettings { ... } object-init form. Both produce identical CommandPlans; the fluent shape stays canonical in docs and the tamp init template. See Build Script Authoring → Two authoring styles for the side-by-side comparison.

Clone this wiki locally