-
Notifications
You must be signed in to change notification settings - Fork 0
Parameter And Secret Injection
How the framework gets values into your build class — three attribute families, each with a different role.
Non-sensitive values that vary per environment. Auto-bound from CLI args, environment variables, and the property's declared default.
[Parameter("Build configuration")]
Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
[Parameter("Target environment", EnvironmentVariable = "DEPLOY_ENVIRONMENT")]
string Environment = "development";
[Parameter] int Verbosity { get; set; } = 1;-
Command-line argument wins. By default the property name maps to a kebab-case flag —
Configuration→--configuration <value>. Both space-separated (--configuration Release) and=(--configuration=Release) forms work. Bare flags (--quiet) are treated astrue. -
Environment variable if the CLI didn't supply one. Default mapping is UPPER_SNAKE_CASE —
Configuration→CONFIGURATION. Override viaEnvironmentVariable = "...". - Property default — whatever you assigned in the declaration.
ParameterBinder handles string, bool, enum (case-insensitive), nullable wrappers, and any IConvertible scalar (int, long, double, decimal, DateTime, etc.).
For bool:
| Input | Result |
|---|---|
true, True, 1
|
true |
false, False, 0
|
false |
| anything else | error |
[Parameter(Name = "env", EnvironmentVariable = "DEPLOY_ENV")]
string Environment = "dev";
// CLI: --env prod
// Env: DEPLOY_ENV=prodThe decorated member's type must be Secret, not string. The Secret class:
-
ToString()always returns<Secret:Name>— never the value - The value is reachable only via the internal
Reveal()method (visible to the runner and tests; not to wrappers or build scripts) - Every secret in a
CommandPlan.Secretslist is auto-registered with the runner's redaction table; matching values are scrubbed from log output, dry-run rendering, and child-process stdout/stderr passed through Tamp's writer
[Secret("Container registry password", EnvironmentVariable = "REGISTRY_PASSWORD")]
Secret RegistryPassword;- CI vendor's secret store (when running in a recognised CI and the vendor exposes secrets via env vars — they typically do)
- Local secret store (DPAPI on Windows, libsecret on Linux, Keychain on macOS) — planned, not yet implemented
- Environment variable
- Interactive prompt if attached to a TTY — planned, not yet implemented
Status: v0 implements step 3 (env-var resolution). Steps 1, 2, 4 are scheduled for v0.x.
Wrapper APIs that take secrets accept the typed Secret parameter directly:
Docker.Login(s => s
.SetServer("registry.example.com")
.SetUsername("ci")
.SetPassword(RegistryPassword)); // Secret, not stringWrappers that accept secrets should prefer stdin or file-based passing where the underlying tool supports it (docker login --password-stdin, gh auth login --with-token, etc.) — keeps the secret out of the OS process table. CommandPlan.StandardInput is the framework primitive for that.
Auto-compute a value from the build context and inject it into a property. Distinct from [Parameter] — these don't read from CLI/env, they construct from the repository state.
Loads the .slnx or .sln discovered at the repository root.
[Solution] readonly Solution Solution;
// Now you can read:
Solution.Path // AbsolutePath to the solution file
Solution.Name // Solution display name
Solution.Projects // IReadOnlyList<SolutionProject>
Solution.GetProject("MyApp")?.Path // case-insensitive lookupOverride the discovery with an explicit path:
[Solution(Path = "src/MySpecific.slnx")] readonly Solution Sln;Reads .git/HEAD, .git/refs/heads/*, and .git/config directly — no shell-out to git. Works on machines without git installed, as long as the working tree was cloned.
[GitRepository] readonly GitRepository Git;
Git.Branch // current branch name, null on detached HEAD
Git.Commit // 40-char SHA
Git.RemoteUrl // origin URL
Git.IsDetachedHead
Git.Root // working-tree root (AbsolutePath)Auto-resolves a .NET tool from NuGet. Decorated property must be Tool.
[NuGetPackage("dotnet-sonarscanner", Version = "9.0.0")] readonly Tool SonarScanner;
// later:
Target SonarBegin => _ => _.Executes(() =>
ProcessRunner.Execute(SonarScanner.Plan("begin", "/k:my-project")));First use installs into a Tamp-managed cache at TampBuild.TemporaryDirectory / "nuget-tools" / "{packageId}-{version}". Subsequent runs reuse the cache.
Three escape hatches for special cases:
| Property | When to use |
|---|---|
Version = "..." |
Pin the version. Default = latest stable (one network round-trip on first use). |
ExecutableName = "..." |
The installed binary name differs from the package id. |
LocalCachePath = "..." |
Skip install entirely; resolve from a pre-staged directory. For locked-down environments that can't reach nuget.org. |
UseSystemPath = true |
Legacy. Resolves against the OS PATH. Prefer [FromPath] (below) — it has the same intent with better naming and full Windows extension probing (.cmd / .exe / .bat / .ps1, not just .exe). |
Resolves a native executable from the OS PATH. The canonical way to inject non-NuGet tools (Docker, yarn, git, docker-compose, etc.).
[FromPath("docker")] readonly Tool Docker = null!;
[FromPath("yarn")] readonly Tool Yarn = null!;
[FromPath("gh", Optional = true)] readonly Tool? Gh = null;| Property | Default | Behavior |
|---|---|---|
Optional = false |
throws at injection time | Injects null when the executable can't be found; consumer can Requires(() => tool != null) or branch on it. |
On Windows, the resolver probes extensions in order: .cmd, .exe, .bat, .ps1, then the bare name. On POSIX, only the bare name. PATH-order is preserved (first hit wins). For inline use without an attribute: Tool.FromPath("yarn") (throws) or Tool.TryFromPath("yarn") (returns null).
Resolves a tool installed under <projectRoot>/node_modules/.bin/<name> — the standard place workspace-local Node tools live (turbo, vite, vitest, tsc, eslint, etc.).
[FromNodeModules("turbo")] readonly Tool Turbo = null!;
[FromNodeModules("vite")] readonly Tool Vite = null!;
[FromNodeModules("vitest", Optional = true)] readonly Tool? Vitest = null;| Property | Default | Behavior |
|---|---|---|
ProjectRoot |
TampBuild.RootDirectory |
Override for nested workspaces (e.g. ProjectRoot = "frontend"). Relative paths resolve against RootDirectory; absolute paths win as-is. |
Optional |
false |
Same semantics as [FromPath]. |
Important: the binary doesn't exist until yarn install (or npm install) runs. Targets that consume the injected Tool must DependsOn(nameof(YarnInstall)):
Target FrontendBuild => _ => _
.DependsOn(nameof(YarnInstall))
.Executes(() => Turbo.Run(Turbo, s => s.AddTask("build")));On Windows, the resolver probes .cmd (the standard yarn-installed shim) before .exe. For inline use: Tool.FromNodeModules("turbo", projectRoot) / Tool.TryFromNodeModules(...). The "not found" error message includes a yarn install hint.
Subclass ValueInjectionAttribute and override GetValue. The framework discovers it automatically — same code path as [Solution] and [GitRepository].
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class GitVersionAttribute : ValueInjectionAttribute
{
public override object? GetValue(MemberInfo member, Type memberType)
{
// … resolve and return your value …
}
}This is the extensibility point for ecosystem authors who want to add [NpmPackage], [LatestNuGetVersion], [GitVersion], [Pulumi], etc. — drop a new package into the consumer's Build.csproj and the attribute Just Works.
ParameterBinder runs once at the start of Execute<T>(args), in this order:
- ValueInjection attributes (Solution, GitRepository, NuGetPackage, FromPath, FromNodeModules, custom)
-
[Parameter]bindings
Targets are then collected, the graph is built, and the executor takes over. Properties are stable for the lifetime of the build invocation.
[Secret] resolution is intentionally lazy — secrets resolve only when a target that requires them is about to run, so unrelated targets don't fail because an unrelated secret isn't available. (v0 currently resolves all secrets at bind time too; the lazy variant lands when interactive prompting + local stores arrive.)
-
Failure Handling —
Requires(hard precondition) vsOnlyWhen(silent skip) -
Tooling Primitives —
AbsolutePath,Verify - Build Script Authoring — full target DSL surface
Start here
Modules
- Module Catalog (canonical list, 50+ satellites)
- .NET toolchain
- Containers
- JS toolchain
- Supply-chain security
Analyzers
Tooling
Execution
CI integration
Editor integration
Migration
Reference
Project