Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,4 @@ coverage/**
lcov.info
launchSettings.json
tests/**/*.received.*
.nuget/
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ nuget-license [options]
| `-i`, `--input <FILE>` | Project or solution file to analyze. |
| `-ji`, `--json-input <FILE>` | JSON file with an array of project/solution files to analyze. See [docs/input-json.md](docs/input-json.md). |
| `-t`, `--include-transitive` | Include transitive dependencies. |
| `-a`, `--allowed-license-types <FILE>` | JSON file listing allowed license types. See [docs/allowed-licenses-json.md](docs/allowed-licenses-json.md). |
| `-ignore`, `--ignored-packages <FILE>` | JSON file listing package names to ignore (supports wildcards). See [docs/ignored-packages-json.md](docs/ignored-packages-json.md). |
| `-mapping`, `--licenseurl-to-license-mappings <FILE>` | JSON dictionary mapping license URLs to license types. See [docs/licenseurl-mappings-json.md](docs/licenseurl-mappings-json.md). |
| `-file-mapping`, `--licensefile-to-license-mappings <FILE>` | JSON dictionary mapping license files to license types. Paths are relative to the JSON file. See [docs/licensefile-mappings-json.md](docs/licensefile-mappings-json.md). |
| `-override`, `--override-package-information <FILE>` | JSON list to override package/license info. See [docs/override-package-json.md](docs/override-package-json.md). |
| `-d`, `--license-information-download-location <FOLDER>` | Download all license files to the specified folder. |
| `-o`, `--output <TYPE>` | Output format: `Table`, `Markdown`, `Json` or `JsonPretty` (default: Table). See [docs/output-json.md](docs/output-json.md) for JSON format details. |
| `-err`, `--error-only` | Only show validation errors. |
| `-include-ignored`, `--include-ignored-packages` | Include ignored packages in output. |
| `-exclude-projects`, `--exclude-projects-matching <PATTERN\|FILE>` | Exclude projects by name or pattern (supports wildcards or JSON file). See [docs/exclude-projects-json.md](docs/exclude-projects-json.md). |
| `-a`, `--allowed-license-types <FILE\|LIST>` | Specifies allowed license types. You can provide either a JSON file listing allowed license types (see [docs/allowed-licenses-json.md](docs/allowed-licenses-json.md)), or a semicolon-separated list (e.g., `"MIT;Apache-2.0;BSD-3-Clause"`). |
| `-ignore`, `--ignored-packages <FILE\|LIST>` | Specifies package names to ignore during validation (supports wildcards). You can provide either a JSON file (see [docs/ignored-packages-json.md](docs/ignored-packages-json.md)), or a semicolon-separated list (e.g., `"Package1;Package2"`). |
| `-mapping`, `--licenseurl-to-license-mappings <FILE>` | Specifies a JSON dictionary mapping license URLs to license types. See [docs/licenseurl-mappings-json.md](docs/licenseurl-mappings-json.md). |
| `-file-mapping`, `--licensefile-to-license-mappings <FILE>` | Specifies a JSON dictionary mapping license files to license types. Paths are relative to the JSON file. See [docs/licensefile-mappings-json.md](docs/licensefile-mappings-json.md). |
| `-override`, `--override-package-information <FILE>` | Specifies a JSON list to override package/license information. See [docs/override-package-json.md](docs/override-package-json.md). |
| `-d`, `--license-information-download-location <FOLDER>` | Specifies a folder where all license files will be downloaded. |
| `-o`, `--output <TYPE>` | Specifies the output format: `Table`, `Markdown`, `Json` or `JsonPretty` (default: Table). See [docs/output-json.md](docs/output-json.md) for JSON format details. |
| `-err`, `--error-only` | When set, only validation errors are shown. |
| `-include-ignored`, `--include-ignored-packages` | When set, ignored packages are included in the output. |
| `-exclude-projects`, `--exclude-projects-matching <FILE\|LIST>` | Specifies projects to exclude from analysis. You can provide either a JSON file (see [docs/exclude-projects-json.md](docs/exclude-projects-json.md)), or a semicolon-separated list (e.g., `"*Test*;Legacy*"`). Wildcards (`*`) are supported. |
| `-isp`, `--include-shared-projects` | Include shared projects (`.shproj`). |
| `-f`, `--target-framework <TFM>` | Analyze for a specific Target Framework Moniker. |
| `-fo`, `--file-output <FILE>` | Write output to a file instead of console. |
Expand Down Expand Up @@ -128,10 +128,28 @@ nuget-license -i MySolution.sln

### Use a custom allowed license list

Using a JSON file:
```ps
nuget-license -i MyProject.csproj -a allowed-licenses.json
```

Using inline semicolon-separated values:
```ps
nuget-license -i MyProject.csproj -a "MIT;Apache-2.0;BSD-3-Clause"
```

### Ignore specific packages

Using a JSON file:
```ps
nuget-license -i MyProject.csproj -ignore ignored-packages.json
```

Using inline semicolon-separated values (supports wildcards):
```ps
nuget-license -i MyProject.csproj -ignore "InternalPackage1;InternalPackage2;Test*"
```

### Generate pretty JSON output

```ps
Expand Down
43 changes: 38 additions & 5 deletions docs/allowed-licenses-json.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Allowed Licenses JSON File Format (`--allowed-license-types`)
# Allowed Licenses (`--allowed-license-types`)

The allowed licenses JSON file is used with the `-a` or `--allowed-license-types` option to specify which license types are permitted.
The `-a` or `--allowed-license-types` option is used to specify which license types are permitted.

## Format
## Input Format

The file must contain a JSON array of license identifiers (SPDX or custom):
You can provide the allowed licenses in two ways:

### 1. JSON File

Provide a path to a JSON file containing an array of license identifiers (SPDX or custom):

```json
[
Expand All @@ -14,4 +18,33 @@ The file must contain a JSON array of license identifiers (SPDX or custom):
]
```

Each entry should be a string representing a license type.
**Example usage:**
```bash
nuget-license -i MyProject.csproj -a allowed-licenses.json
```

### 2. Inline Semicolon-Separated List

Provide a semicolon-separated list of license identifiers directly on the command line:

**Example usage:**
```bash
nuget-license -i MyProject.csproj -a "MIT;Apache-2.0;BSD-3-Clause"
```

**Note:** When using inline format, make sure to quote the entire list to prevent shell interpretation of special characters.

## Format Detection

The tool automatically detects whether the input is a file path or an inline list:
- If a file exists at the specified path, it will be read as a JSON file
- Otherwise, the input will be parsed as a semicolon-separated inline list

**Important:** If you have a file in your current directory with a name that matches your inline value (e.g., a file named "MIT"), the tool will read from the file instead of parsing it as an inline value. In such cases, use a different file name or provide a full/relative path to disambiguate.

## License Identifiers

Each entry should be a string representing a license type. License identifiers can be:
- SPDX license identifiers (e.g., `MIT`, `Apache-2.0`, `GPL-3.0`)
- Custom license names
- SPDX license expressions (e.g., `MIT OR Apache-2.0`, `GPL-2.0-or-later WITH Classpath-exception-2.0`)
45 changes: 40 additions & 5 deletions docs/exclude-projects-json.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Exclude Projects JSON File Format (`--exclude-projects-matching`)
# Exclude Projects (`--exclude-projects-matching`)

The exclude projects JSON file is used with the `-exclude-projects` or `--exclude-projects-matching` option to specify projects to exclude from analysis.
The `-exclude-projects` or `--exclude-projects-matching` option is used to specify projects to exclude from analysis. Common use cases include excluding test projects, sample projects, or build tools from the analysis when working with a solution file.

## Format
## Input Format

The file must contain a JSON array of project names or patterns. Wildcards (`*`) are supported:
You can provide the excluded projects in two ways:

### 1. JSON File

Provide a path to a JSON file containing an array of project names or patterns. Wildcards (`*`) are supported:

```json
[
Expand All @@ -14,4 +18,35 @@ The file must contain a JSON array of project names or patterns. Wildcards (`*`)
]
```

Each entry should be a string representing a project name or pattern.
**Example usage:**
```bash
nuget-license -i MySolution.sln -exclude-projects exclude-projects.json
```

### 2. Inline Semicolon-Separated List

Provide a semicolon-separated list of project names or patterns directly on the command line. Wildcards (`*`) are supported:

**Example usage:**
```bash
nuget-license -i MySolution.sln -exclude-projects "*Test*;SampleProject;Legacy*"
```

**Note:** When using inline format, make sure to quote the entire list to prevent shell interpretation of wildcards.

## Format Detection

The tool automatically detects whether the input is a file path or an inline list:
- If a file exists at the specified path, it will be read as a JSON file
- Otherwise, the input will be parsed as a semicolon-separated inline list

**Important:** If you have a file in your current directory with a name that matches your inline value, the tool will read from the file instead of parsing it as an inline value. In such cases, use a different file name or provide a full/relative path to disambiguate.

## Project Names

Each entry should be a string representing a project name or pattern:
- Exact match: `"ProjectName"`
- Prefix wildcard: `"ProjectName*"`
- Suffix wildcard: `"*ProjectName"`
- Contains: `"*PartialName*"`
- Multiple wildcards: `"*Test*.csproj"`
46 changes: 41 additions & 5 deletions docs/ignored-packages-json.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# Ignored Packages JSON File Format (`--ignored-packages`)
# Ignored Packages (`--ignored-packages`)

The ignored packages JSON file is used with the `-ignore` or `--ignored-packages` option to specify NuGet packages to skip during validation.
The `-ignore` or `--ignored-packages` option is used to specify NuGet packages to skip during validation.

## Format
**Note:** Even though packages are ignored, their transitive dependencies are not ignored unless explicitly listed.

The file must contain a JSON array of package names. Wildcards (`*`) are supported:
## Input Format

You can provide the ignored packages in two ways:

### 1. JSON File

Provide a path to a JSON file containing an array of package names. Wildcards (`*`) are supported:

```json
[
Expand All @@ -14,4 +20,34 @@ The file must contain a JSON array of package names. Wildcards (`*`) are support
]
```

Each entry should be a string representing a package name or pattern.
**Example usage:**
```bash
nuget-license -i MyProject.csproj -ignore ignored-packages.json
```

### 2. Inline Semicolon-Separated List

Provide a semicolon-separated list of package names directly on the command line. Wildcards (`*`) are supported:

**Example usage:**
```bash
nuget-license -i MyProject.csproj -ignore "MyCompany.*;TestPackage;LegacyLib*"
```

**Note:** When using inline format, make sure to quote the entire list to prevent shell interpretation of wildcards.

## Format Detection

The tool automatically detects whether the input is a file path or an inline list:
- If a file exists at the specified path, it will be read as a JSON file
- Otherwise, the input will be parsed as a semicolon-separated inline list

**Important:** If you have a file in your current directory with a name that matches your inline value, the tool will read from the file instead of parsing it as an inline value. In such cases, use a different file name or provide a full/relative path to disambiguate.

## Package Names

Each entry should be a string representing a package name or pattern:
- Exact match: `"PackageName"`
- Prefix wildcard: `"PackageName*"`
- Suffix wildcard: `"*PackageName"`
- Contains: `"*PartialName*"`
69 changes: 38 additions & 31 deletions src/NuGetLicense/LicenseValidationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,9 @@ public async Task<int> HandleAsync(CommandLineOptions options, CancellationToken

private Stream GetOutputStream(string? destinationFile)
{
if (destinationFile is null)
{
return _outputStream;
}
return _fileSystem.File.Open(_fileSystem.Path.GetFullPath(destinationFile), FileMode.Create, FileAccess.Write, FileShare.None);
return destinationFile is null
? _outputStream
: _fileSystem.File.Open(_fileSystem.Path.GetFullPath(destinationFile), FileMode.Create, FileAccess.Write, FileShare.None);
}

private async Task WriteValidationExceptions(IReadOnlyCollection<Exception> validationExceptions)
Expand Down Expand Up @@ -150,14 +148,41 @@ private CustomPackageInformation[] GetOverridePackageInformation(string? overrid
return JsonSerializer.Deserialize<CustomPackageInformation[]>(_fileSystem.File.ReadAllText(overridePackageInformation), serializerOptions)!;
}

private string[] GetAllowedLicenses(string? allowedLicenses)
private string[] ParseStringArrayOrFile(string? value)
{
if (allowedLicenses == null)
if (value == null)
{
return Array.Empty<string>();
}

return JsonSerializer.Deserialize<string[]>(_fileSystem.File.ReadAllText(allowedLicenses))!;
// Check if the value is a path to an existing file
if (_fileSystem.File.Exists(value))
{
try
{
string fileContent = _fileSystem.File.ReadAllText(value);
string[]? result = JsonSerializer.Deserialize<string[]>(fileContent);
return result ?? throw new ArgumentException($"File '{value}' contains invalid JSON: expected an array of strings but got null.");
}
catch (JsonException ex)
{
throw new ArgumentException($"Failed to parse JSON file '{value}': {ex.Message}", ex);
}
}

// Parse as semicolon-separated inline values
string[] parts = value.Split([';'], StringSplitOptions.RemoveEmptyEntries);
// Trim each part manually for .NET Framework compatibility
for (int i = 0; i < parts.Length; i++)
{
parts[i] = parts[i].Trim();
}
return Array.FindAll(parts, part => part.Length > 0);
}

private string[] GetAllowedLicenses(string? allowedLicenses)
{
return ParseStringArrayOrFile(allowedLicenses);
}

private IImmutableDictionary<Uri, string> GetLicenseMappings(string? licenseMapping)
Expand All @@ -174,27 +199,12 @@ private IImmutableDictionary<Uri, string> GetLicenseMappings(string? licenseMapp

private string[] GetIgnoredPackages(string? ignoredPackages)
{
if (ignoredPackages == null)
{
return Array.Empty<string>();
}

return JsonSerializer.Deserialize<string[]>(_fileSystem.File.ReadAllText(ignoredPackages))!;
return ParseStringArrayOrFile(ignoredPackages);
}

private string[] GetExcludedProjects(string? excludedProjects)
{
if (excludedProjects == null)
{
return Array.Empty<string>();
}

if (_fileSystem.File.Exists(excludedProjects))
{
return JsonSerializer.Deserialize<string[]>(_fileSystem.File.ReadAllText(excludedProjects))!;
}

return [excludedProjects];
return ParseStringArrayOrFile(excludedProjects);
}

private string[] GetInputFiles(string? inputFile, string? inputJsonFile)
Expand All @@ -204,12 +214,9 @@ private string[] GetInputFiles(string? inputFile, string? inputJsonFile)
return [inputFile];
}

if (inputJsonFile != null)
{
return JsonSerializer.Deserialize<string[]>(_fileSystem.File.ReadAllText(inputJsonFile))!;
}

throw new ArgumentException("Please provide an input file using --input or --input-file-json");
return inputJsonFile != null
? JsonSerializer.Deserialize<string[]>(_fileSystem.File.ReadAllText(inputJsonFile))!
: throw new ArgumentException("Please provide an input file using --input or --json-input");
}

private static IReadOnlyCollection<ProjectWithReferencedPackages> GetPackagesPerProject(
Expand Down
Loading