From 5f06c848272106695dd9fd505cc805880d4db9e8 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:09:43 +0200 Subject: [PATCH 1/3] :wrench: add husky and license check --- .config/dotnet-tools.json | 27 +++++++++ .github/workflows/_static-analysis.yml | 6 ++ .gitignore | 5 +- .husky/check-licenses.sh | 78 ++++++++++++++++++++++++++ .husky/licenses.allowed | 11 ++++ .husky/licenses.allowed-packages | 6 ++ .husky/pre-commit | 22 ++++++++ .husky/pre-push | 4 ++ .husky/run-unit-tests.sh | 27 +++++++++ .husky/task-runner.json | 30 ++++++++++ 10 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100755 .husky/check-licenses.sh create mode 100644 .husky/licenses.allowed create mode 100644 .husky/licenses.allowed-packages create mode 100755 .husky/pre-commit create mode 100755 .husky/pre-push create mode 100755 .husky/run-unit-tests.sh create mode 100644 .husky/task-runner.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000..ba970de2 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "mindee.cli": { + "version": "4.6.0", + "commands": [ + "mindee" + ], + "rollForward": false + }, + "dotnet-delice": { + "version": "2.1.0", + "commands": [ + "dotnet-delice" + ], + "rollForward": false + }, + "husky": { + "version": "0.9.1", + "commands": [ + "husky" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.github/workflows/_static-analysis.yml b/.github/workflows/_static-analysis.yml index acdd4cce..ce138726 100644 --- a/.github/workflows/_static-analysis.yml +++ b/.github/workflows/_static-analysis.yml @@ -21,3 +21,9 @@ jobs: - name: Run dotnet format run: dotnet format --verify-no-changes + + - name: Restore local tools (dotnet-delice) + run: dotnet tool restore + + - name: Check dependency licenses against whitelist + run: bash .husky/check-licenses.sh diff --git a/.gitignore b/.gitignore index 33795d06..c89f00b8 100644 --- a/.gitignore +++ b/.gitignore @@ -374,5 +374,6 @@ _site # StrongName files *.snk *.snk.b64 -# Local CLI publish. -dotnet-tools.json + +# dotnet-delice output +/licenses.json diff --git a/.husky/check-licenses.sh b/.husky/check-licenses.sh new file mode 100755 index 00000000..3c6a141c --- /dev/null +++ b/.husky/check-licenses.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# +# License whitelist check for NuGet dependencies. +# +# Runs `dotnet delice` and fails if any package uses a license expression that +# isn't listed in .husky/licenses.allowed, unless the package name appears in +# .husky/licenses.allowed-packages. +# +# Requires: dotnet, jq, bash. No Python. +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SLN="$SCRIPT_DIR/../Mindee.sln" +# SPDX license-list cache that delice re-creates in the repository root on each run. +SPDX_CACHE="$SCRIPT_DIR/../licenses.json" +ALLOWED_FILE="$SCRIPT_DIR/licenses.allowed" +ALLOWED_PKGS_FILE="$SCRIPT_DIR/licenses.allowed-packages" + +command -v jq >/dev/null 2>&1 || { + echo "[husky] jq is required for the license check but was not found in PATH." >&2 + exit 2 +} + +TMP_JSON="$(mktemp -t delice-XXXXXX.json)" +trap 'rm -f "$TMP_JSON" "$SPDX_CACHE"' EXIT + +echo "[husky] Running dotnet delice on ${SLN} ..." +dotnet delice "$SLN" -j --json-output "$TMP_JSON" >/dev/null + +strip_comments() { + # Drop blank lines and '#' comments, trim trailing whitespace. + sed -e 's/[[:space:]]*$//' -e '/^[[:space:]]*#/d' -e '/^[[:space:]]*$/d' "$1" +} + +ALLOWED_LICENSES="$(strip_comments "$ALLOWED_FILE" || true)" +ALLOWED_PACKAGES="$(strip_comments "$ALLOWED_PKGS_FILE" 2>/dev/null || true)" + +# Emit one tab-separated record per package: projectnameversionexpression +# We deliberately avoid jq's @tsv, which doubles backslashes (breaking matches +# against expressions like `licenses\LICENSE.txt`). +RECORDS="$(jq -r ' + .projects[] + | .projectName as $p + | .licenses[] + | .expression as $e + | .packages[] + | [$p, .name, (.version // "?"), $e] | join("\t") +' "$TMP_JSON")" + +violations="" +while IFS=$'\t' read -r project name version expression; do + [ -z "${name:-}" ] && continue + # Allowed license expression? + if printf '%s\n' "$ALLOWED_LICENSES" | grep -Fxq -- "$expression"; then + continue + fi + # Per-package exception? + if [ -n "$ALLOWED_PACKAGES" ] && printf '%s\n' "$ALLOWED_PACKAGES" | grep -Fxq -- "$name"; then + continue + fi + violations+=$'\n'" - ${name}@${version} (license: ${expression}) [project: ${project}]" +done <<< "$RECORDS" + +if [ -n "$violations" ]; then + { + echo "Disallowed package licenses detected:" + # De-duplicate while preserving order. + printf '%s\n' "$violations" | awk 'NF && !seen[$0]++' + echo + echo "Either remove the offending dependency, add the license expression to" + echo ".husky/licenses.allowed, or add the package name to" + echo ".husky/licenses.allowed-packages after review." + } >&2 + exit 1 +fi + +echo "License check passed: all packages use whitelisted licenses." diff --git a/.husky/licenses.allowed b/.husky/licenses.allowed new file mode 100644 index 00000000..d8ad2dc1 --- /dev/null +++ b/.husky/licenses.allowed @@ -0,0 +1,11 @@ +# One SPDX-style license expression per line. Blank lines and '#' comments allowed. +# Any package whose license expression is NOT in this list will fail the pre-push check, +# unless the package name appears in licenses.allowed-packages. +MIT +Apache-2.0 +BSD-3-Clause +LGPL-3.0-or-later +Apache-2.0 AND MIT +Microsoft Software License +licenses\LICENSE.txt +Project References diff --git a/.husky/licenses.allowed-packages b/.husky/licenses.allowed-packages new file mode 100644 index 00000000..704ccaa8 --- /dev/null +++ b/.husky/licenses.allowed-packages @@ -0,0 +1,6 @@ +# Per-package overrides for cases where delice cannot detect the SPDX license +# (legacy NuGet license structure). Verified manually to be acceptable. +# One "PackageName" per line (no version). +Microsoft.NETFramework.ReferenceAssemblies +Microsoft.NETFramework.ReferenceAssemblies.net472 +Microsoft.NETFramework.ReferenceAssemblies.net48 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..65130a63 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,22 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +## husky task runner examples ------------------- +## Note : for local installation use 'dotnet' prefix. e.g. 'dotnet husky' + +## run all tasks +#husky run + +### run all tasks with group: 'group-name' +#husky run --group group-name + +## run task with name: 'task-name' +#husky run --name task-name + +## pass hook arguments to task +#husky run --args "$1" "$2" + +## or put your custom commands ------------------- +#echo 'Husky.Net is awesome!' + +dotnet husky run --group pre-commit diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..3af48fa0 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +dotnet husky run --group pre-push diff --git a/.husky/run-unit-tests.sh b/.husky/run-unit-tests.sh new file mode 100755 index 00000000..b3bac2bc --- /dev/null +++ b/.husky/run-unit-tests.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# +# Run unit tests for the target frameworks that are actually supported on the +# current OS. .NET Framework targets (net472 / net48) require Windows because +# Docnet.Core's native PDF binaries don't load under Mono on *nix. +# +# Mirrors the matrix used in .github/workflows/_test-units.yml. +# +set -euo pipefail + +PROJECT="tests/Mindee.UnitTests/Mindee.UnitTests.csproj" + +case "$(uname -s 2>/dev/null || echo Windows)" in + MINGW*|MSYS*|CYGWIN*|Windows*) + FRAMEWORKS=("net6.0" "net8.0" "net10.0" "net472" "net48") + ;; + *) + FRAMEWORKS=("net8.0" "net10.0") + ;; +esac + +echo "[husky] Running unit tests for: ${FRAMEWORKS[*]}" + +for tfm in "${FRAMEWORKS[@]}"; do + echo "[husky] --- $tfm ---" + dotnet test "$PROJECT" -f "$tfm" --nologo -v:quiet +done diff --git a/.husky/task-runner.json b/.husky/task-runner.json new file mode 100644 index 00000000..90a25c42 --- /dev/null +++ b/.husky/task-runner.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://alirezanet.github.io/Husky.Net/schema.json", + "tasks": [ + { + "name": "dotnet-format-staged", + "group": "pre-commit", + "command": "dotnet", + "args": [ "format", "Mindee.sln", "--include", "${staged}", "--verify-no-changes", "--no-restore" ], + "include": [ "**/*.cs" ] + }, + { + "name": "build", + "group": "pre-commit", + "command": "dotnet", + "args": [ "build", "Mindee.sln", "--nologo", "-clp:NoSummary", "-v:quiet" ] + }, + { + "name": "license-check", + "group": "pre-push", + "command": "bash", + "args": [ ".husky/check-licenses.sh" ] + }, + { + "name": "unit-tests", + "group": "pre-push", + "command": "bash", + "args": [ ".husky/run-unit-tests.sh" ] + } + ] +} From 705513960df5dd5ced434c5ec769fa9b75c29cf3 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 3 Sep 2026 18:00:47 +0200 Subject: [PATCH 2/3] remove from pre-commit hook --- .husky/task-runner.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.husky/task-runner.json b/.husky/task-runner.json index 90a25c42..e47dad2b 100644 --- a/.husky/task-runner.json +++ b/.husky/task-runner.json @@ -14,12 +14,6 @@ "command": "dotnet", "args": [ "build", "Mindee.sln", "--nologo", "-clp:NoSummary", "-v:quiet" ] }, - { - "name": "license-check", - "group": "pre-push", - "command": "bash", - "args": [ ".husky/check-licenses.sh" ] - }, { "name": "unit-tests", "group": "pre-push", From 457dcd69c31e5b4d55d4228071728afe59677857 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 3 Sep 2026 22:33:41 +0200 Subject: [PATCH 3/3] Fix formatting command in task-runner.json Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .husky/task-runner.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.husky/task-runner.json b/.husky/task-runner.json index e47dad2b..df56d78c 100644 --- a/.husky/task-runner.json +++ b/.husky/task-runner.json @@ -4,9 +4,9 @@ { "name": "dotnet-format-staged", "group": "pre-commit", - "command": "dotnet", - "args": [ "format", "Mindee.sln", "--include", "${staged}", "--verify-no-changes", "--no-restore" ], - "include": [ "**/*.cs" ] + "command": "dotnet", + "args": [ "format", "Mindee.sln", "--include", "${staged}", "--verify-no-changes" ], + "include": [ "**/*.cs" ] }, { "name": "build",