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