Update publish-all-nuget-packages.yml #51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish NuGet Packages | |
| on: | |
| push: | |
| branches: [main] | |
| release: | |
| types: [published, prerelease] | |
| permissions: | |
| contents: write | |
| packages: write | |
| jobs: | |
| publish-nuget: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| package: | |
| - { id: BlueTeam-Tools, folder: BlueTeam-Tools, nuspec: BlueTeam-Tools.nuspec, tags: "powershell security eventlog incidentresponse blueteam automation enterprise" } | |
| - { id: Core-ScriptLibrary, folder: Core-ScriptLibrary, nuspec: Core-ScriptLibrary.nuspec, tags: "powershell scripting nuget modular automation dotnet" } | |
| - { id: ITSM-Templates-SVR, folder: ITSM-Templates-SVR, nuspec: ITSM-Templates-SVR.nuspec, tags: "itsm server configuration documentation automation enterprise" } | |
| - { id: ITSM-Templates-WKS, folder: ITSM-Templates-WKS, nuspec: ITSM-Templates-WKS.nuspec, tags: "itsm workstation configuration powershell registry automation windows" } | |
| - { id: SysAdmin-Tools, folder: SysAdmin-Tools, nuspec: SysAdmin-Tools.nuspec, tags: "sysadmin activedirectory gpo powershell sso network wsus security automation enterprise" } | |
| fail-fast: false | |
| steps: | |
| - name: 🧾 Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: recursive | |
| - name: 🛠️ Debug workflow start | |
| run: | | |
| echo "Workflow started for package ${{ matrix.package.id }}" | |
| echo "Event: ${{ github.event_name }}" | |
| echo "Repository: ${{ github.repository }}" | |
| echo "Commit: ${{ github.sha }}" | |
| echo "Listing repository contents:" | |
| ls -R | |
| echo "Checking for .nuspec files in root:" | |
| ls -l *.nuspec || echo "No .nuspec files found in root" | |
| echo "Checking for LICENSE file:" | |
| ls -l LICENSE || echo "LICENSE not found in root" | |
| echo "Checking for CHANGELOG.md:" | |
| ls -l CHANGELOG.md || echo "CHANGELOG.md not found in root" | |
| echo "Checking for package directories:" | |
| for pkg in BlueTeam-Tools Core-ScriptLibrary ITSM-Templates-SVR ITSM-Templates-WKS SysAdmin-Tools; do | |
| ls -lR "$pkg" || echo "Directory $pkg not found" | |
| done | |
| echo "Checking .NET SDK version:" | |
| dotnet --version | |
| echo "Checking for mono and nuget availability:" | |
| which mono || echo "mono not found" | |
| which nuget || echo "nuget not found" | |
| - name: 📦 Install dependencies | |
| run: | | |
| echo "Installing mono-complete and xmlstarlet" | |
| sudo apt-get update | |
| sudo apt-get install -y mono-complete xmlstarlet | |
| echo "Checking mono version:" | |
| mono --version | |
| echo "Installing nuget.exe" | |
| wget -O nuget.exe https://dist.nuget.org/win-x86-commandline/v6.11.0/nuget.exe | |
| chmod +x nuget.exe | |
| sudo mv nuget.exe /usr/local/bin/nuget | |
| echo "Verifying nuget command:" | |
| mono /usr/local/bin/nuget help | head -n 1 | |
| - name: 📁 Prepare and stage files | |
| id: prepare | |
| run: | | |
| pkg="${{ matrix.package.id }}" | |
| folder="${{ matrix.package.folder }}" | |
| nuspec="${{ matrix.package.nuspec }}" | |
| echo "Preparing package $pkg" | |
| if [ ! -f "$nuspec" ]; then | |
| echo "❌ Error: NuSpec file $nuspec missing in root, skipping package" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| mkdir -p ReadmeCopies nupkg-out "tmp-$pkg" | |
| if [ -d "$folder" ]; then | |
| echo "Copying package directory $folder to tmp-$pkg" | |
| cp -r "$folder/." "tmp-$pkg/" | |
| else | |
| echo "⚠️ Warning: Folder $folder missing, proceeding with .nuspec only" | |
| fi | |
| cp "$nuspec" "tmp-$pkg/$nuspec" | |
| if [ -f "$folder/README.md" ]; then | |
| cp "$folder/README.md" "ReadmeCopies/$pkg-README.md" | |
| cp "ReadmeCopies/$pkg-README.md" "tmp-$pkg/$pkg-README.md" | |
| echo "Copied README.md for $pkg" | |
| else | |
| echo "⚠️ Warning: README.md not found for $pkg" | |
| fi | |
| if [ -f "$folder/icon.png" ]; then | |
| cp "$folder/icon.png" "tmp-$pkg/icon.png" | |
| echo "Copied icon.png for $pkg" | |
| else | |
| echo "⚠️ Warning: icon.png not found for $pkg" | |
| fi | |
| if [ -f "LICENSE" ]; then | |
| cp "LICENSE" "tmp-$pkg/LICENSE" | |
| echo "Copied LICENSE to tmp-$pkg" | |
| else | |
| echo "❌ Error: LICENSE file not found in root, skipping package" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "Listing tmp-$pkg contents:" | |
| ls -lR "tmp-$pkg" | |
| echo "Checking .nuspec file contents:" | |
| cat "$nuspec" | |
| echo "Validating .nuspec metadata:" | |
| xmlstarlet val "$nuspec" || { echo "❌ Error: Invalid XML in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| xmlstarlet sel -t -m "//metadata/id" -v . "$nuspec" > /dev/null || { echo "❌ Error: Missing id in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| xmlstarlet sel -t -m "//metadata/version" -v . "$nuspec" > /dev/null || { echo "❌ Error: Missing version in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| xmlstarlet sel -t -m "//metadata/authors" -v . "$nuspec" > /dev/null || { echo "❌ Error: Missing authors in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| xmlstarlet sel -t -m "//metadata/description" -v . "$nuspec" > /dev/null || { echo "❌ Error: Missing description in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| xmlstarlet sel -t -m "//metadata/license" -v . "$nuspec" > /dev/null || { echo "❌ Error: Missing license in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| xmlstarlet sel -t -m "//metadata/projectUrl" -v . "$nuspec" > /dev/null || { echo "❌ Error: Missing projectUrl in $nuspec"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } | |
| echo "Validating .nuspec file paths:" | |
| invalid_paths=false | |
| grep '<file src="' "$nuspec" | while read -r line ; do | |
| src=$(echo "$line" | sed -E 's/.*src="([^"]+)".*/\1/') | |
| src_expanded=$(echo "$src" | sed "s|\${{ matrix.package.folder }}|$folder|g") | |
| if [[ "$src" == */*/* ]]; then | |
| dir=$(echo "$src_expanded" | cut -d'/' -f1) | |
| if [ -d "tmp-$pkg/$dir" ]; then | |
| echo "Path $src_expanded exists in tmp-$pkg" | |
| else | |
| echo "❌ Error: Directory $dir for $src in $nuspec does not exist in tmp-$pkg" | |
| invalid_paths=true | |
| fi | |
| elif [ -f "tmp-$pkg/$src_expanded" ]; then | |
| echo "Path $src_expanded exists in tmp-$pkg" | |
| else | |
| echo "❌ Error: File $src_expanded in $nuspec does not exist in tmp-$pkg" | |
| invalid_paths=true | |
| fi | |
| done | |
| if [ "$invalid_paths" = "true" ]; then | |
| echo "❌ Error: Invalid paths found in $nuspec, skipping package" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| - name: 📖 Enhance .nuspec with rich metadata | |
| if: steps.prepare.outputs.skip != 'true' | |
| run: | | |
| pkg="${{ matrix.package.id }}" | |
| nuspec="${{ matrix.package.nuspec }}" | |
| folder="${{ matrix.package.folder }}" | |
| version=$(xmlstarlet sel -t -m "//metadata/version" -v . "$nuspec" || echo "0.0.0") | |
| if [ -z "$version" ]; then | |
| echo "⚠️ Warning: Version not found in $nuspec, using default" | |
| version="0.0.0" | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| changelog=$(awk "/^## \\[$version\\]/,/^## \\[/" CHANGELOG.md | sed '1d;/^## \\[/d' || echo "No changelog found for version $version") | |
| changelog_sanitized=$(printf "%s" "$changelog" | sed ':a;N;$!ba;s/\n/\\n/g;s/"/\\"/g') | |
| echo "notes=$changelog_sanitized" >> "$GITHUB_OUTPUT" | |
| # Update .nuspec with rich metadata | |
| cp "tmp-$pkg/$nuspec" "tmp-$pkg/$nuspec.bak" | |
| xmlstarlet ed \ | |
| -u "//metadata/releaseNotes" -v "$changelog_sanitized" \ | |
| -s "//metadata[not(releaseNotes)]" -t elem -n "releaseNotes" -v "$changelog_sanitized" \ | |
| -u "//metadata/tags" -v "${{ matrix.package.tags }}" \ | |
| -s "//metadata[not(tags)]" -t elem -n "tags" -v "${{ matrix.package.tags }}" \ | |
| -s "//metadata[not(icon)]" -t elem -n "icon" -v "icon.png" \ | |
| -s "//metadata[not(repository)]" -t elem -n "repository" -v "" \ | |
| -s "//metadata/repository" -t attr -n "type" -v "git" \ | |
| -s "//metadata/repository" -t attr -n "url" -v "https://github.com/${{ github.repository }}" \ | |
| -s "//metadata/repository" -t attr -n "branch" -v "${{ github.ref_name }}" \ | |
| -s "//metadata/repository" -t attr -n "commit" -v "${{ github.sha }}" \ | |
| -s "//metadata[not(copyright)]" -t elem -n "copyright" -v "Copyright (c) 2025 Luiz Hamilton Roberto da Silva" \ | |
| -s "//metadata[not(owners)]" -t elem -n "owners" -v "Luiz Hamilton Roberto da Silva" \ | |
| -s "//metadata[not(requireLicenseAcceptance)]" -t elem -n "requireLicenseAcceptance" -v "false" \ | |
| "tmp-$pkg/$nuspec" > "tmp-$pkg/$nuspec.tmp" | |
| mv "tmp-$pkg/$nuspec.tmp" "tmp-$pkg/$nuspec" | |
| # Add README.md and icon.png to files if present | |
| if [ -f "tmp-$pkg/$pkg-README.md" ]; then | |
| xmlstarlet ed -s "//files" -t elem -n "file" -v "" \ | |
| -s "//files/file[last()]" -t attr -n "src" -v "$pkg-README.md" \ | |
| -s "//files/file[last()]" -t attr -n "target" -v "content" \ | |
| "tmp-$pkg/$nuspec" > "tmp-$pkg/$nuspec.tmp" | |
| mv "tmp-$pkg/$nuspec.tmp" "tmp-$pkg/$nuspec" | |
| fi | |
| if [ -f "tmp-$pkg/icon.png" ]; then | |
| xmlstarlet ed -s "//files" -t elem -n "file" -v "" \ | |
| -s "//files/file[last()]" -t attr -n "src" -v "icon.png" \ | |
| -s "//files/file[last()]" -t attr -n "target" -v "images" \ | |
| "tmp-$pkg/$nuspec" > "tmp-$pkg/$nuspec.tmp" | |
| mv "tmp-$pkg/$nuspec.tmp" "tmp-$pkg/$nuspec" | |
| fi | |
| echo "Updated .nuspec with rich metadata:" | |
| cat "tmp-$pkg/$nuspec" | |
| - name: 📦 Pack NuGet package | |
| if: steps.prepare.outputs.skip != 'true' | |
| run: | | |
| pkg="${{ matrix.package.id }}" | |
| nuspec="${{ matrix.package.nuspec }}" | |
| if [ ! -d "tmp-$pkg" ]; then | |
| echo "❌ Error: Directory tmp-$pkg missing, skipping package" | |
| exit 0 | |
| fi | |
| cd "tmp-$pkg" | |
| mono /usr/local/bin/nuget pack "$nuspec" \ | |
| -OutputDirectory ../nupkg-out \ | |
| -Symbols -SymbolPackageFormat snupkg \ | |
| -NonInteractive \ | |
| || { echo "❌ Error: Failed to pack $nuspec"; exit 0; } | |
| - name: 🚀 Push to GitHub Packages | |
| if: steps.prepare.outputs.skip != 'true' && (github.event_name == 'push' || github.event_name == 'release') | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| for pkg_path in nupkg-out/*.nupkg nupkg-out/*.snupkg; do | |
| if [ -f "$pkg_path" ]; then | |
| echo "Pushing $pkg_path to GitHub Packages" | |
| mono /usr/local/bin/nuget push "$pkg_path" \ | |
| -Source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ | |
| -ApiKey "$GITHUB_TOKEN" \ | |
| -NonInteractive -SkipDuplicate | |
| fi | |
| done | |
| - name: 🚀 Push to NuGet.org | |
| if: steps.prepare.outputs.skip != 'true' && (github.event_name == 'push' || github.event_name == 'release') | |
| env: | |
| NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} | |
| run: | | |
| for pkg_path in nupkg-out/*.nupkg nupkg-out/*.snupkg; do | |
| if [ -f "$pkg_path" ]; then | |
| echo "Pushing $pkg_path to NuGet.org" | |
| mono /usr/local/bin/nuget push "$pkg_path" \ | |
| -Source "https://api.nuget.org/v3/index.json" \ | |
| -ApiKey "$NUGET_API_KEY" \ | |
| -NonInteractive -SkipDuplicate | |
| fi | |
| done | |
| - name: 🏷️ Create Git tag | |
| if: steps.prepare.outputs.skip != 'true' && (github.event_name == 'push' || github.event_name == 'release') | |
| run: | | |
| tag="${{ matrix.package.id }}-v${{ steps.prepare.outputs.version }}" | |
| if git tag --list | grep -q "^$tag$"; then | |
| echo "Tag $tag already exists, skipping creation" | |
| else | |
| git config user.name "github-actions" | |
| git config user.email "github-actions@github.com" | |
| git tag "$tag" | |
| git push origin "$tag" | |
| fi | |
| - name: 🧹 Clean up | |
| if: always() | |
| run: | | |
| rm -rf "tmp-${{ matrix.package.id }}" ReadmeCopies nupkg-out || true |