nulic 0.0.4

dotnet tool install --global nulic --version 0.0.4
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local nulic --version 0.0.4
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=nulic&version=0.0.4
                    
nuke :add-package nulic --version 0.0.4
                    

nulic

A .NET global tool that collects and produces a license disclosure package for all NuGet dependencies in a project.

CI

What it does

Given a solution, project, or folder, nulic:

  1. Enumerates all NuGet packages — direct and transitive
  2. Downloads or copies the actual license text for each package
  3. Writes a third-party-notices/ folder next to the solution containing:
    • One license text file per unique license (shared across packages using the same SPDX id)
    • A nulic-packages.json manifest with full metadata per package

The goal is a complete, shippable artifact — every package gets an actual license file on disk.

Install

dotnet tool install -g nulic

Usage

nulic [<path>] [options]
Argument / Option Description
<path> Solution file, project file, or folder. Default: .
-d, --show-defaults Print the default nulic.json to stdout and exit
-l, --log-level Log verbosity: Verbose, Debug, Information (default), Warning, Error
-m, --merge <dir> Merge a third-party-notices/ directory from another nulic-processed project (repeatable)
-o, --output <dir> Output folder for license files and report. Default: <path>/third-party-notices

Examples

# Run on the current directory (auto-discovers solution)
nulic

# Run on a specific solution
nulic path/to/MyApp.sln

# Only show warnings and errors
nulic --log-level Warning

# Combine licenses from two sub-projects into one disclosure package
nulic MyApp.sln --merge ../firmware/third-party-notices --merge ../safety/third-party-notices

# Write output to a custom folder
nulic MyApp.sln --output D:/artifacts/third-party-notices

nulic.json

On first run, nulic creates a nulic.json next to the solution as a starting template.
Use nulic --show-defaults to print the default config.

{
  "exclude": [],      // glob patterns for project files to exclude from scanning
  "ignore":  [],      // packages to ignore — id:* or author:* patterns
  "allow":   [],      // SPDX allowlist; non-empty enables allowlist mode (exits 1 on violation)
  "overrides": []     // patch metadata or inject packages without a real NuGet entry
}

Ignore patterns

Patterns are glob-style (* = any characters). The id: prefix is optional — a bare pattern also matches against package id.

"ignore": [
  "Tyrell.Corp.*",             // id glob — id: prefix is optional
  "id:*Nexus.Test*",           // id: prefix is explicit but equivalent
  "author:Rick Deckard",       // match on author name (glob)
  "developmentDependency",     // packages marked developmentDependency in packages.config
  "PrivateAssets"              // packages with PrivateAssets=all in PackageReference
]

Overrides

Override or inject a package entry — useful for packages with dead license URLs or internal packages. All fields except id are optional:

"overrides": [
  {
    // All available fields:
    "id":         "DeathStar.TurboLaser",
    "version":    "3.0.0",                             // pin to a specific version (omit = all versions)
    "license":    "LicenseRef-Imperial",               // SPDX expression or custom LicenseRef
    "licenseUrl": "licenses/IMPERIAL_LICENSE.txt",     // local path or https://
    "authors":    ["Emperor Palpatine"],
    "projectUrl": "https://empire.gov/deathstar",
    "copyright":  "Copyright © 19 BBY Galactic Empire. All sectors reserved."
  },
  {
    // Minimal — just fix the license URL for a package with a dead link:
    "id":         "HanSolo.Kessel",
    "licenseUrl": "https://raw.githubusercontent.com/hansolo/kessel/main/LICENSE"
  }
]

Allowlist (CI gate)

When allow is non-empty, nulic validates every package against the list and exits with code 1 if any violation is found — suitable for use as a CI gate:

"allow": [
  "MIT",
  "Apache-2.0",
  "BSD-3-Clause",
  "WITH LicenseRef-linking-exception"   // permits GPL files carrying a linking exception
]

Compound SPDX expressions (e.g. MIT AND LicenseRef-foo) must have every component covered by the allowlist.

NOASSERTION

If nulic cannot obtain or identify a license for a package, the license field in nulic-packages.json is set to NOASSERTION and a warning is printed. Common causes:

  • The package has no license metadata and no recognizable license file
  • The license URL is dead or returns an unrecognizable format
  • The package is not in the local NuGet cache (run dotnet restore first)

Fix with an override:

"overrides": [
  {
    "id": "Some.Package",
    "license": "MIT",
    "licenseUrl": "https://raw.githubusercontent.com/org/repo/main/LICENSE"
  }
]

Merging projects (--merge)

When a product bundles multiple independently-built components (e.g. a main application together with subsystems from different repos), each component should first be processed by nulic on its own. Then use --merge to produce a combined disclosure package:

# Step 1: process each component independently (in their own CI)
nulic HAL9000/           # produces HAL9000/third-party-notices/
nulic AE35Unit/          # produces AE35Unit/third-party-notices/

# Step 2: produce the combined disclosure for the product
nulic Discovery/ --merge ../HAL9000/third-party-notices --merge ../AE35Unit/third-party-notices

The merge step:

  • Reads nulic-packages.json from each merged directory
  • Copies license files into the target third-party-notices/ folder (skips identical files, errors on content mismatch)
  • Unions all packages, deduplicating by Id + Version
  • Validates the combined set against the target project's allow list
  • Regenerates third-party-notices.md from the combined data

Running nulic on the nulic repo itself produces (abbreviated):

third-party-notices/
  MIT.txt                          # shared — AngleSharp, System.CommandLine, Textify, ...
  Apache-2.0.txt                   # shared — all NuGet.* packages, Serilog, ...
  Microsoft.Build.18.6.3/
    notices/THIRDPARTYNOTICES.txt  # supplementary file from the package
  ...                              # one entry per unique package
  nulic-packages.json

nulic-packages.json entry (real example from nulic's own dependencies):

{
  "id": "AngleSharp",
  "version": "1.4.0",
  "authors": ["AngleSharp"],
  "projectUrl": "https://anglesharp.github.io/",
  "copyright": "Copyright 2013-2025, AngleSharp.",
  "license": "MIT",
  "licenseUrl": "https://licenses.nuget.org/MIT",
  "licenseFiles": ["MIT.txt"]
}

Supported project types

Project type Package source
SDK-style .csproj / .fsproj / .vbproj obj/project.assets.json (requires dotnet restore)
Classic .NET Framework .csproj packages.config
Native C++ .vcxproj packages.config
Solutions .sln / .slnx All constituent projects
Folder Auto-discovers first .sln / .slnx, then .csproj, etc.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.0.4 46 5/25/2026
0.0.3 42 5/25/2026
0.0.2 52 5/25/2026
0.0.1 46 5/25/2026
0.0.1-alpha1 53 5/25/2026