Nullean.Make.Fs 0.25.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Nullean.Make.Fs --version 0.25.0
                    
NuGet\Install-Package Nullean.Make.Fs -Version 0.25.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nullean.Make.Fs" Version="0.25.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nullean.Make.Fs" Version="0.25.0" />
                    
Directory.Packages.props
<PackageReference Include="Nullean.Make.Fs" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nullean.Make.Fs --version 0.25.0
                    
#r "nuget: Nullean.Make.Fs, 0.25.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nullean.Make.Fs@0.25.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nullean.Make.Fs&version=0.25.0
                    
Install as a Cake Addin
#tool nuget:?package=Nullean.Make.Fs&version=0.25.0
                    
Install as a Cake Tool

Nullean.Make.Fs

Typed, namespaced, exhaustive F# build targets. Your targets are DU cases, and a case you forget to handle in Bind is a compiler warning (FS0025). Target blocks are Proc.Fs shell { } blocks, so commands are sh $"…" lines.

open Nullean.Make.Fs
open Proc.Fs

type TestOptions = { Filter: string option }

type PkgTarget =
    /// Pack the NuGet packages
    | Generate
    /// Validate the packed NuGet packages
    | Validate

type Target =
    /// Clean known output locations
    | Clean
    /// Run build
    | Build
    /// Run build then tests
    | Test of TestOptions
    | Pkg of PkgTarget
    /// Test, then pack and validate
    | Release

[<EntryPoint>]
let main argv =
    let app    = MakeApp<Target>("build", None)
    let config = app.Option<string>("--config", desc = "Build configuration", defaultValue = "Release")

    app.Bind <| function
        | Clean        -> target [] { sh $"dotnet clean" }
        | Build        -> target [Clean] { sh $"dotnet build -c {config.Value}" }
        | Test o       -> target [Build] {
                              match o.Filter with
                              | Some f -> sh $"dotnet test -c {config.Value} --filter:{f}"
                              | None   -> sh $"dotnet test -c {config.Value}" }
        | Pkg Generate -> target [] { sh $"dotnet pack -c Release -o build/output" }
        | Pkg Validate -> target [] { sh $"dotnet nupkg-validator build/output" }
        | Release      -> command {
                              requires [Test { Filter = None }]
                              composes [Pkg Generate; Pkg Validate] }

    app.RunAsync(argv).GetAwaiter().GetResult()
build --help
build test --filter Unit --config Debug
build pkg --help
build release
build release -s        # skip the tests, run only pkg generate, pkg validate
  Commands:  (pipeline entry points — compose and sequence targets)
  release    Test, then pack and validate

  Targets:  (atomic steps — can also be run directly)
  clean    Clean known output locations
  build    Run build  (depends on: clean)
  test     Run build then tests  (depends on: build)

  Namespaces:
  pkg

The package depends on Proc.Fs 0.17, so open Proc.Fs brings in sh, the captures and exec { } without another reference.


Targets: target [deps] { ... }

target [Clean] { ... } declares a target that runs after Clean; -s / --single-target skips the dependencies. The block is a Proc.Fs shell { } block that runs only when the target does:

| Info -> target [] {
      let! sha = text $"git rev-parse --short HEAD"     // standard out as one trimmed string
      let! files = lines $"git ls-files"                 // standard out as a list of lines
      for file in files |> List.filter (fun f -> f.EndsWith ".fs") do
          sh $"dotnet fantomas {file}"                   // {file} is always one argument
      printfn $"formatted commit {sha}" }
Inside target [] { } Does
sh $"…" runs the command; a failure fails the target
let! s = text $"…" standard out as one trimmed string
let! ls = lines $"…" standard out as a list of lines
let! code = exitCode $"…" the exit code, without failing
let! ok = succeeds $"…" whether the exit code is 0, without failing
cd, pushd, popd, env, setEnvs, unsetEnv change the commands that follow
use! _ = inDir "src", use! _ = withEnv [...] change them until the end of the enclosing scope

Plain F# works too: if, match, loops, printfn, and exec { } from earlier versions. sh splits only the text you wrote, and each {value} stays one argument whatever it contains, so no value can start another command. See the Proc.Fs README for quoting, pipes (|) and multi-line commands.

The first command that fails fails the target, and RunAsync returns 1.

Commands: command { ... }

| Release -> command {
      requires [PristineCheck; Test { Filter = None }]   // gates, skipped under -s
      composes [Pkg Generate; Pkg Validate] }            // the work, always runs

executes (...) adds a body that runs after every composed target. Like a target block, it is deferred. Use shell { } inside it to run commands:

| Publish -> command {
      requires [Release]
      composes [CreateGithubRelease]
      executes (
          shell {
              let! sha = text $"git rev-parse --short HEAD"
              printfn $"published {sha}" }) }

Descriptions from doc comments

A target's description is the /// doc comment on its DU case. The comment is read from the XML documentation file, so the build project needs <GenerateDocumentationFile>true</GenerateDocumentationFile>. A .fsx script produces no documentation file. In a script, pass a description with Make.target (below), or the target shows its name.

Namespaces via nested DUs

A case whose payload is another DU becomes a CLI namespace: Pkg of PkgTarget gives pkg generate and pkg validate, and pkg --help lists them. Two sub-DUs may share case names, such as Validate; qualify the ambiguous one in Bind (| Schema SchemaTarget.Validate ->).

Per-target typed arguments

A case with a record payload binds the record's fields from CLI flags at execution time: Test of TestOptions with { Filter: string option } takes --filter. A dependency can name any value of the case: Test { Filter = Some "Unit" } resolves to the test target.

Global options

let verbose = app.Flag("--verbose", short = "-v", desc = "Enable verbose output")
let token   = app.Option<string option>("--token", desc = "API token", defaultValue = None)

Read them in target blocks through .Value. Help shows each option's type, such as --config <string>, and --token <string?> for an option type. A value that does not parse, or a missing value, exits 2 before any target runs. Enum options bind by name, ignoring case.

The explicit functions

target and command are shorthand for these, which also take a description and give the body an FsContext:

Make.target  (deps: 'T list) (desc: string) (body: FsContext -> unit)
Make.command (requires: 'T list) (composes: 'T list)
Make.composer (requires: 'T list) (composes: 'T list) (body: FsContext -> unit)   // like command { executes (...) }
failBuild "message"                                                               // abort with exit code 1

Installation

<PackageReference Include="Nullean.Make.Fs" Version="0.24.0" />

Requires .NET 11. The package pulls in Nullean.Make (the underlying C# engine) and Proc.Fs transitively.

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.30.0 347 11/12/2026
0.29.0 241 11/12/2026
0.28.0 461 11/12/2026
0.27.2 170 11/11/2026
0.27.1 168 11/11/2026
0.25.0 666 11/11/2026
0.24.0 50 11/11/2026
0.23.0 47 11/11/2026