Skip to content

Tamp Yarn

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

Tamp.Yarn

Wrapper for the Yarn Berry 4 CLI — the package manager flavour HoldFast's frontend uses. Classic Yarn 1.x is a different shape and would live in a separate sibling package; the .V4 pin marks the major boundary.

using Tamp.Yarn.V4;

Full reference lives in the satellite repo: https://github.com/tamp-build/tamp-yarn.

Verbs

Yarn.Install(tool, s =>)              // yarn install
Yarn.Run(tool, s =>)                  // yarn run <script>
Yarn.Workspaces.Foreach(tool, s =>)   // yarn workspaces foreach …
Yarn.Workspaces.List(tool, s =>)      // yarn workspaces list
Yarn.Npm.Publish(tool, s =>)          // yarn npm publish (OTP typed as Secret)
Yarn.Npm.Whoami(tool, s =>)
Yarn.Cache.Clean(tool, s =>)
Yarn.Version(tool, s =>)
Yarn.Raw(tool,)                       // escape hatch

Yarn is resolved via the system PATH because the binary location depends on the corepack / nvm setup on the runner:

[NuGetPackage("yarn", UseSystemPath = true)]
readonly Tool YarnTool;

CI behaviour to know about

Immutable installs. Under CI=true, Yarn Berry auto-enables enableImmutableInstalls. That's almost always what you want — but the integration test that creates a lockfile from scratch needs YARN_ENABLE_IMMUTABLE_INSTALLS=false. The wrapper doesn't override either way; you set it via SetEnv("YARN_ENABLE_IMMUTABLE_INSTALLS", "false") where appropriate.

Windows binary extension. The corepack-installed yarn ships as yarn.cmd / yarn.ps1 on Windows, not bare yarn. The [NuGetPackage(UseSystemPath=true)] resolver handles this — but if you're manually constructing a Tool in tests, walk PATH with the Windows-aware extension list (yarn.cmd, yarn.exe, yarn.bat, yarn.ps1, yarn).

packageManager field required. Yarn 2+ rejects any project whose package.json lacks a "packageManager": "yarn@4.x.x" field. Not the wrapper's job to add it; just be aware your fixture needs it.

Quick example — HoldFast frontend install + npm publish

[NuGetPackage("yarn", UseSystemPath = true)]
readonly Tool YarnTool = null!;

[Secret("npm publish OTP", EnvironmentVariable = "NPM_OTP")]
readonly Secret? NpmOtp = null!;

Target Install => _ => _.Executes(() =>
    Yarn.Install(YarnTool, s => s
        .SetImmutable()
        .SetWorkingDirectory(RootDirectory / "frontend")));

Target Publish => _ => _
    .DependsOn(nameof(Install))
    .Requires(() => NpmOtp != null)
    .Executes(() =>
        Yarn.Npm.Publish(YarnTool, s => s
            .SetAccess("public")
            .SetOtp(NpmOtp)
            .SetWorkingDirectory(RootDirectory / "frontend" / "packages" / "sdk")));

The Otp value flows into the runner's redaction table — anything that subsequently logs the OTP gets scrubbed.

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