diff --git a/.github/workflows/code-scan.yaml b/.github/workflows/code-scan.yaml
new file mode 100644
index 000000000..2c8ddd612
--- /dev/null
+++ b/.github/workflows/code-scan.yaml
@@ -0,0 +1,40 @@
+name: Code Scan
+
+on:
+ pull_request:
+ branches:
+ - main
+ push:
+ branches:
+ - main
+ schedule:
+ - cron: '0 0 * * 1' # Every Monday at 00:00 UTC
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ security-events: write
+
+jobs:
+ scan:
+ name: Trivy Code Scan
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@v0.35.0
+ with:
+ scan-type: 'fs'
+ scan-ref: '.'
+ format: 'sarif'
+ output: 'trivy-results.sarif'
+
+ - name: Upload Trivy results to GitHub Security
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
+ uses: github/codeql-action/upload-sarif@v4
+ with:
+ sarif_file: 'trivy-results.sarif'
+ category: 'Trivy'
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 000000000..230950b56
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,82 @@
+name: Publish
+
+on:
+ workflow_call:
+ inputs:
+ version:
+ required: true
+ type: string
+ description: 'Version to publish (e.g., 1.0.0)'
+ notes:
+ required: true
+ type: string
+ description: 'Release notes for the NuGet package'
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ publish:
+ name: Publish to NuGet
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ global-json-file: src/global.json
+
+ - name: Build
+ run: |
+ dotnet build \
+ --configuration Release \
+ src/ShinyPDF/ShinyPDF.csproj \
+ /p:Version=${{ inputs.version }} \
+ /p:AssemblyVersion=${{ inputs.version }} \
+ /p:FileVersion=${{ inputs.version }} \
+ /p:InformationalVersion=${{ inputs.version }}
+
+ - name: Write package release notes file
+ env:
+ PACKAGE_RELEASE_NOTES_RAW: ${{ inputs.notes }}
+ run: |
+ printf '%s' "$PACKAGE_RELEASE_NOTES_RAW" > "$RUNNER_TEMP/release-notes.txt"
+
+ - name: Pack NuGet package
+ run: |
+ dotnet pack \
+ --configuration Release \
+ --no-build \
+ --no-restore \
+ --output . \
+ src/ShinyPDF/ShinyPDF.csproj \
+ /p:Version=${{ inputs.version }} \
+ /p:PackageVersion=${{ inputs.version }} \
+ /p:AssemblyVersion=${{ inputs.version }} \
+ /p:FileVersion=${{ inputs.version }} \
+ /p:InformationalVersion=${{ inputs.version }} \
+ /p:PackageReleaseNotesFile="$RUNNER_TEMP/release-notes.txt"
+
+ - name: NuGet login
+ uses: NuGet/login@v1
+ id: login
+ with:
+ user: ${{ secrets.NUGET_USER }}
+
+ - name: Publish to NuGet
+ env:
+ NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }}
+ run: |
+ dotnet nuget push "*.nupkg" \
+ --api-key "$NUGET_API_KEY" \
+ --source https://api.nuget.org/v3/index.json \
+ --skip-duplicate
+
+ dotnet nuget push "*.snupkg" \
+ --api-key "$NUGET_API_KEY" \
+ --source https://api.nuget.org/v3/index.json \
+ --skip-duplicate
diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml
new file mode 100644
index 000000000..e56ce3a11
--- /dev/null
+++ b/.github/workflows/pull-request.yaml
@@ -0,0 +1,50 @@
+name: Pull Request
+
+on:
+ pull_request:
+ branches:
+ - main
+
+permissions:
+ contents: read
+ checks: write
+ pull-requests: write
+
+jobs:
+ build:
+ name: Build and Test
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ global-json-file: src/global.json
+
+ - name: Restore dependencies
+ run: dotnet restore src/ShinyPDF.slnx
+
+ - name: Build
+ run: dotnet build src/ShinyPDF.slnx --configuration Release --no-restore
+
+ - name: Run unit tests
+ run: |
+ dotnet test \
+ src/ShinyPDF.UnitTests/ShinyPDF.UnitTests.csproj \
+ --configuration Release \
+ --no-build \
+ --results-directory src/ShinyPDF.UnitTests/TestResults \
+ --verbosity normal \
+ --logger "trx;LogFileName=test-results.trx"
+
+ - name: Parse and report test results
+ if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository }}
+ uses: dorny/test-reporter@v3
+ with:
+ name: Unit Test Results
+ path: 'src/ShinyPDF.UnitTests/TestResults/test-results.trx'
+ reporter: 'dotnet-trx'
+ fail-on-error: false
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 000000000..c7932f688
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,51 @@
+name: Release
+
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ id-token: write
+
+jobs:
+ release:
+ name: Create GitHub release
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.changelog.outputs.version }}
+ clean_changelog: ${{ steps.changelog.outputs.clean_changelog }}
+ skipped: ${{ steps.changelog.outputs.skipped }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - name: Generate version from Conventional Commits
+ id: changelog
+ uses: TriPSs/conventional-changelog-action@v6
+ with:
+ fallback-version: 1.0.0
+
+
+ - name: Create GitHub release
+ if: ${{ steps.changelog.outputs.skipped == 'false' }}
+ uses: softprops/action-gh-release@v3
+ with:
+ tag_name: ${{ steps.changelog.outputs.tag }}
+ name: ${{ steps.changelog.outputs.version }}
+ body: ${{ steps.changelog.outputs.clean_changelog }}
+
+ publish:
+ name: Publish to NuGet
+ needs: release
+ if: ${{ needs.release.result == 'success' && needs.release.outputs.skipped == 'false' }}
+ uses: ./.github/workflows/publish.yaml
+ secrets: inherit
+ with:
+ version: ${{ needs.release.outputs.version }}
+ notes: ${{ needs.release.outputs.clean_changelog }}
diff --git a/src/ShinyPDF/Resources/Description.md b/src/ShinyPDF/Resources/Description.md
deleted file mode 100644
index 5a39b9782..000000000
--- a/src/ShinyPDF/Resources/Description.md
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-# ShinyPDF
-
-ShinyPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API. Shiny PDF is based on the latest fully open source version of [QuestPDF](https://github.com/QuestPDF/QuestPDF).
-
-
-
| 👨💻 | -Build production-ready PDFs in pure C# with a code-first workflow that fits naturally into your existing development process. | -
| 🧱 | -Compose rich layouts with predictable building blocks: text, images, borders, tables, layers, headers, footers, and more. | -
| ⚙️ | -Rely on a layout engine purpose-built for document generation, with robust paging, measurement, and rendering behavior. | -
| 📖 | -Stay productive with a concise Fluent API and full IntelliSense discoverability across the entire document DSL. | -
| 🔗 | -No proprietary template language. Use modern .NET features, reusable abstractions, and the tools you already trust. | -