diff --git a/.github/workflows/nuget-reference-check.yml b/.github/workflows/nuget-reference-check.yml new file mode 100644 index 0000000..3b6b2d5 --- /dev/null +++ b/.github/workflows/nuget-reference-check.yml @@ -0,0 +1,142 @@ +name: "nuget package reference check" + +on: + push: + pull_request: + schedule: + - cron: '0 8 * * *' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 2 + + - name: Setup .NET Environment + uses: actions/setup-dotnet@v5.1.0 + with: + dotnet-version: 10.0.x + + - name: Install dependencies + run: dotnet restore Kalliope.sln + + - name: Build + run: dotnet build Kalliope.sln --no-restore /p:ContinuousIntegrationBuild=true + + - name: Check for outdated packages + id: outdated + run: | + set -e + + # Packages to ignore (SDK/runtime-managed) + IGNORE_PACKAGES="Microsoft\.NETCore\.Platforms|Microsoft\.NETCore\.Targets" + + dotnet list Kalliope.sln package --outdated --include-transitive > outdated-raw.log + + # Filter out ignored core packages + grep -v -E "$IGNORE_PACKAGES" outdated-raw.log > outdated.log || true + + # Print full outdated report (including test projects) to action log + echo "=== Full outdated packages report ===" + cat outdated.log + + # Build issue log: exclude test project sections + # dotnet list output groups packages under project headers like: + # Project `ProjectName` has the following updates available: + # We remove sections for *.Tests projects + awk ' + /^Project .*.Tests/ { skip=1; next } + /^Project / { skip=0 } + !skip { print } + ' outdated.log > outdated-issue.log + + # Check if non-test outdated packages exist (look for > lines indicating actual packages) + if grep -q ">" outdated-issue.log; then + echo "Outdated packages found (non-test)" + echo "outdated=true" >> $GITHUB_OUTPUT + else + echo "No outdated packages found in non-test projects" + echo "outdated=false" >> $GITHUB_OUTPUT + fi + + - name: Check for deprecated packages + id: deprecated + run: | + set -e + dotnet list Kalliope.sln package --deprecated --include-transitive > deprecated.log + if [ -s deprecated.log ]; then + echo "Deprecated packages found" + echo "deprecated=true" >> $GITHUB_OUTPUT + else + echo "No deprecated packages found" + echo "deprecated=false" >> $GITHUB_OUTPUT + fi + + - name: Check for vulnerable packages + id: vulnerable + run: | + set -e + dotnet list Kalliope.sln package --vulnerable --include-transitive > vulnerabilities.log + if grep -q -i "\bcritical\b\|\bhigh\b\|\bmoderate\b\|\blow\b" vulnerabilities.log; then + echo "Security Vulnerabilities found" + echo "vulnerable=true" >> $GITHUB_OUTPUT + else + echo "No Security Vulnerabilities found" + echo "vulnerable=false" >> $GITHUB_OUTPUT + fi + + - name: Create GitHub Issue if issues found + if: steps.outdated.outputs.outdated == 'true' || steps.deprecated.outputs.deprecated == 'true' || steps.vulnerable.outputs.vulnerable == 'true' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + + let issueBody = `### NuGet Package Issues Detected in [Kalliope](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY})\n\n`; + + if ('${{ steps.outdated.outputs.outdated }}' === 'true') { + const outdatedLog = fs.readFileSync('outdated-issue.log', 'utf8'); + issueBody += `#### Outdated Packages\n\`\`\`\n${outdatedLog}\n\`\`\`\n\n`; + } + + if ('${{ steps.deprecated.outputs.deprecated }}' === 'true') { + const deprecatedLog = fs.readFileSync('deprecated.log', 'utf8'); + issueBody += `#### Deprecated Packages\n\`\`\`\n${deprecatedLog}\n\`\`\`\n\n`; + } + + if ('${{ steps.vulnerable.outputs.vulnerable }}' === 'true') { + const vulnerabilitiesLog = fs.readFileSync('vulnerabilities.log', 'utf8'); + issueBody += `#### Vulnerable Packages\n\`\`\`\n${vulnerabilitiesLog}\n\`\`\`\n\n`; + } + + issueBody += '**Action Required:** Please review and update the affected packages.'; + + const issueTitle = 'NuGet Package Issues Detected'; + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + }); + + const existingIssue = issues.find(issue => issue.title === issueTitle); + + if (existingIssue) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existingIssue.number, + body: `New check results:\n${issueBody}`, + }); + } else { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: issueTitle, + body: issueBody, + labels: ['dependencies', 'maintenance'], + }); + } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..39813c5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,144 @@ +name: Nuget-Release + +on: + workflow_dispatch: + inputs: + version: + description: 'SemVer (e.g., 1.2.3 or 1.2.3-beta.1)' + required: true + type: string + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + + steps: + - uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + ref: ${{ env.DEFAULT_BRANCH }} + + - name: Validate version (SemVer) + id: semver + run: | + set -euo pipefail + VERSION="${{ github.event.inputs.version }}" + if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z\.-]+)?$ ]]; then + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + if [[ "$VERSION" == *-* ]]; then + echo "PRERELEASE=true" >> "$GITHUB_ENV" + else + echo "PRERELEASE=false" >> "$GITHUB_ENV" + fi + else + echo "Provided version '$VERSION' is not SemVer." >&2 + exit 1 + fi + + - name: Setup .NET + uses: actions/setup-dotnet@v5.1.0 + with: + dotnet-version: '10.0.x' + + - name: Set date + run: | + DATE=$(date +'%Y-%m-%d') + echo "DATE=$DATE" >> $GITHUB_ENV + + - name: Update CITATION.cff (version + date) + run: | + # Top-level + sed -i "s/^version:.*/version: \"${VERSION}\"/" CITATION.cff + sed -i "s/^date-released:.*/date-released: \"${DATE}\"/" CITATION.cff + # preferred-citation (two-space indent) + sed -i "s/^ version:.*/ version: \"${VERSION}\"/" CITATION.cff + sed -i "s/^ date-released:.*/ date-released: \"${DATE}\"/" CITATION.cff + + - name: Commit CITATION.cff to default branch + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add CITATION.cff + + if ! git diff --cached --quiet; then + git commit -m "chore: release ${VERSION} (update CITATION.cff)" + git push origin HEAD:${DEFAULT_BRANCH} + else + echo "No changes to CITATION.cff." + fi + + - name: Install xmlstarlet (for parsing csproj) + run: | + sudo apt-get update + sudo apt-get install -y xmlstarlet + + - name: Build RELEASE_NOTES.md from PackageReleaseNotes + run: | + set -euo pipefail + echo "The following items have been fixed in this release:" > RELEASE_NOTES.md + echo "" >> RELEASE_NOTES.md + + mapfile -t CSPROJS < <(git ls-files '*.csproj' | sort) + + for csproj in "${CSPROJS[@]}"; do + # PackageId (fallback to file name) + pkg_id="$(xmlstarlet sel -t -v '(//Project/PropertyGroup/PackageId)[1]' "$csproj" 2>/dev/null || true)" + if [ -z "$pkg_id" ]; then + pkg_id="$(basename "$csproj" .csproj)" + fi + + # Version (fallback to the workflow input) + pkg_version="$(xmlstarlet sel -t -v '(//Project/PropertyGroup/Version)[1]' "$csproj" 2>/dev/null || true)" + if [ -z "$pkg_version" ]; then + pkg_version="${VERSION}" + fi + + # Multiline PackageReleaseNotes + notes="$(xmlstarlet sel -t -v '(//Project/PropertyGroup/PackageReleaseNotes)[1]' "$csproj" 2>/dev/null || true)" + notes="$(printf "%s" "$notes" | sed -e 's/\r$//')" + + if [ -n "$notes" ]; then + printf "**%s [%s]**\n" "$pkg_id" "$pkg_version" >> RELEASE_NOTES.md + while IFS= read -r line; do + trimmed="$(printf "%s" "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + if [ -n "$trimmed" ]; then + cleaned="$(printf "%s" "$trimmed" | sed 's/^- \{0,1\}//')" + printf " - %s\n" "$cleaned" >> RELEASE_NOTES.md + fi + done <<< "$notes" + echo "" >> RELEASE_NOTES.md + fi + done + + echo "----- RELEASE_NOTES.md -----" + cat RELEASE_NOTES.md + + - name: Pack + run: dotnet pack -c Release -o ReleaseBuilds Kalliope.sln + + - name: Push to NuGet + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: dotnet nuget push ReleaseBuilds/*.nupkg -k "$NUGET_API_KEY" -s https://api.nuget.org/v3/index.json --skip-duplicate + + - name: Create annotated tag at this commit + run: | + git tag -a "${VERSION}" -m "Release ${VERSION}" + git push origin "refs/tags/${VERSION}" + + - name: Create draft GitHub release (attach EXE) + uses: softprops/action-gh-release@v2.5.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true + prerelease: ${{ env.PRERELEASE }} + name: "ECoreNetto ${{ env.VERSION }}" + tag_name: ${{ env.VERSION }} + body_path: RELEASE_NOTES.md diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..b83e3e5 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,48 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." + +title: "Kalliope: a set of C# libraries used to read and write ORM2 files" +abstract: "Kalliope is a set of C# libraries used to read and write ORM2 files. Kalliope is used in Starion Group products and projects to perform code generation." +version: "0.17.1" +date-released: "2024-08-12" +url: "https://github.com/STARIONGROUP/Kalliope.git" + +authors: + - given-names: "Sam" + family-names: "Gerené" + affiliation: "Starion Group S.A." + orcid: "https://orcid.org/0009-0000-1848-550X" + - given-names: Antoine + family-names: Théate + affiliation: "Starion Group S.A." + - given-names: Nathanael + family-names: Smiechowski + affiliation: "Starion Group S.A." + - given-names: Alexander + family-names: Van Delft + affiliation: "Starion Group S.A." + - name: "Starion Group S.A." + +repository-code: "https://github.com/STARIONGROUP/Kalliope.git" +license: "Apache-2.0" + +preferred-citation: + type: software + title: "Kalliope: a set of C# libraries used to read and write ORM2 files" + authors: + - given-names: "Sam" + family-names: "Gerené" + orcid: "https://orcid.org/0009-0000-1848-550X" + - given-names: Antoine + family-names: Théate + affiliation: "Starion Group S.A." + - given-names: Nathanael + family-names: Smiechowski + affiliation: "Starion Group S.A." + - given-names: Alexander + family-names: Van Delft + affiliation: "Starion Group S.A." + - name: "Starion Group S.A." + version: "0.17.1" + url: "https://github.com/STARIONGROUP/Kalliope.git" + date-released: "2024-08-12" diff --git a/Kalliope.sln b/Kalliope.sln index 7ca05ef..ab5b6b0 100644 --- a/Kalliope.sln +++ b/Kalliope.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution NOTICE = NOTICE README.md = README.md CLAUDE.md = CLAUDE.md + CITATION.cff = CITATION.cff EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kalliope.Generator", "Kalliope.Generator\Kalliope.Generator.csproj", "{553B59EB-B9CD-4651-A9AE-2EB161F5A2A1}" @@ -32,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ProjectSection(SolutionItems) = preProject .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml .github\workflows\CodeQuality.yml = .github\workflows\CodeQuality.yml + .github\workflows\nuget-reference-check.yml = .github\workflows\nuget-reference-check.yml + .github\workflows\release.yml = .github\workflows\release.yml EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kalliope.OO", "Kalliope.OO\Kalliope.OO.csproj", "{FF4D9729-2E90-48A4-B41E-DF4827945BB5}" diff --git a/Kalliope/Kalliope.csproj b/Kalliope/Kalliope.csproj index 480ea6c..7e291d8 100644 --- a/Kalliope/Kalliope.csproj +++ b/Kalliope/Kalliope.csproj @@ -6,7 +6,7 @@ The Kalliope POCO class library Kalliope Starion Group S.A. - Copyright 2022-2024 Starion Group S.A. + Copyright 2022-2026 Starion Group S.A. https://kalliope-orm.org/ Apache-2.0 https://github.com/STARIONGROUP/Kalliope.git