Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# EditorConfig for Codout.Framework
# https://editorconfig.org
#
# Repository convention: all text files are UTF-8 with LF line endings and a
# trailing newline. Honored by Visual Studio, VS Code, JetBrains Rider, and
# any editor with EditorConfig support.

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true

# Windows-only batch/cmd scripts must stay CRLF.
[*.{cmd,bat}]
end_of_line = crlf
7 changes: 5 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
# Set default behavior to automatically normalize line endings to LF on commit.
# Repository convention is UTF-8 + LF for all text files. Encoding itself is
# enforced via .editorconfig (charset = utf-8); .gitattributes only handles
# line endings here.
###############################################################################
* text=auto
* text=auto eol=lf

###############################################################################
# Set default behavior for command prompt diff.
Expand Down
29 changes: 29 additions & 0 deletions .github/release-packages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"dynamiclinq": "Codout.DynamicLinq/Codout.DynamicLinq.csproj",
"api": "Codout.Framework.Api/Codout.Framework.Api.csproj",
"api-client": "Codout.Framework.Api.Client/Codout.Framework.Api.Client.csproj",
"api-dto": "Codout.Framework.Api.Dto/Codout.Framework.Api.Dto.csproj",
"application": "Codout.Framework.Application/Codout.Framework.Application.csproj",
"common": "Codout.Framework.Common/Codout.Framework.Common.csproj",
"dp": "Codout.Framework.DP/Codout.Framework.DP.csproj",
"data": "Codout.Framework.Data/Codout.Framework.Data.csproj",
"domain": "Codout.Framework.Domain/Codout.Framework.Domain.csproj",
"ef": "Codout.Framework.EF/Codout.Framework.EF.csproj",
"mongo": "Codout.Framework.Mongo/Codout.Framework.Mongo.csproj",
"nh": "Codout.Framework.NH/Codout.Framework.NH.csproj",
"storage": "Codout.Framework.Storage/Codout.Framework.Storage.csproj",
"storage-azure": "Codout.Framework.Storage.Azure/Codout.Framework.Storage.Azure.csproj",
"image-extensions": "Codout.Image.Extensions/Codout.Image.Extensions.csproj",
"mailer": "Codout.Mailer/Codout.Mailer.csproj",
"mailer-aws": "Codout.Mailer.AWS/Codout.Mailer.AWS.csproj",
"mailer-razor": "Codout.Mailer.Razor/Codout.Mailer.Razor.csproj",
"mailer-sendgrid": "Codout.Mailer.SendGrid/Codout.Mailer.SendGrid.csproj",
"multitenancy": "Codout.Multitenancy/Codout.Multitenancy.csproj",
"softprime-multitenancy": "Codout.Multitenancy/Softprime.Multitenancy.csproj",
"cosmos": "src/NetCore/Codout.Framework.NetCore.Repository.Cosmos/Codout.Framework.NetCore.Repository.Cosmos.csproj",
"documentdb": "src/NetCore/Codout.Framework.NetCore.Repository.DocumentDB/Codout.Framework.NetCore.Repository.DocumentDB.csproj",
"security-argon2": "src/Security/Codout.Security.Argon2/Codout.Security.Argon2.csproj",
"security-bcrypt": "src/Security/Codout.Security.Bcrypt/Codout.Security.Bcrypt.csproj",
"security-core": "src/Security/Codout.Security.Core/Codout.Security.Core.csproj",
"security-scrypt": "src/Security/Codout.Security.Scrypt/Codout.Security.Scrypt.csproj"
}
131 changes: 131 additions & 0 deletions .github/workflows/mass-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Mass Release

# One-shot workflow to publish every Codout.Framework package at the <Version>
# currently pinned in its .csproj. Designed for the initial baseline release
# after migrating to per-package versions (where most packages are at 6.2.2
# and Codout.Framework.EF is at 6.3.0). Idempotent thanks to --skip-duplicate,
# so it is safe to re-run.
#
# Recommended flow:
# 1. Dispatch with dry_run = true to confirm the package list and artifacts.
# 2. Re-dispatch with dry_run = false to publish.

on:
workflow_dispatch:
inputs:
confirm:
description: 'Type PUBLISH to confirm. Anything else is rejected.'
required: true
include_mcp:
description: 'Also release Codout.Framework.Mcp (otherwise excluded; use mcp-release.yml).'
type: boolean
default: false
dry_run:
description: 'Build and pack only; do not push to NuGet.org.'
type: boolean
default: true

env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
CONFIGURATION: Release
MAPPING: .github/release-packages.json
MCP_PROJECT: Codout.Framework.Mcp/src/Tools/Codout.Framework.Mcp/Codout.Framework.Mcp.csproj

jobs:
mass-release:
runs-on: ubuntu-latest
if: inputs.confirm == 'PUBLISH'
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Verify dispatched from master
run: |
set -euo pipefail
git fetch origin master --quiet
if ! git merge-base --is-ancestor HEAD origin/master; then
echo "::error::Mass release must be dispatched from a commit on master."
echo "Currently at $(git rev-parse HEAD); origin/master tip is $(git rev-parse origin/master)."
exit 1
fi
echo "Dispatched from a commit on master: OK"

- name: Restore + build + test (solution)
run: |
dotnet restore Codout.Framework.sln
dotnet build Codout.Framework.sln --configuration "$CONFIGURATION" --no-restore
dotnet test Codout.Framework.sln --configuration "$CONFIGURATION" --no-build --verbosity normal

- name: Pack every package
run: |
set -euo pipefail
mkdir -p ./artifacts

# Out-of-solution projects (Cosmos, DocumentDB, etc.) need their own
# restore/build, so we deliberately do NOT pass --no-build here.
for pkg in $(jq -r 'keys[]' "$MAPPING"); do
project=$(jq -r --arg p "$pkg" '.[$p]' "$MAPPING")
echo "::group::Pack $pkg ($project)"
dotnet pack "$project" --configuration "$CONFIGURATION" --output ./artifacts
echo "::endgroup::"
done

if [ "${{ inputs.include_mcp }}" = "true" ]; then
echo "::group::Pack mcp ($MCP_PROJECT)"
dotnet pack "$MCP_PROJECT" --configuration "$CONFIGURATION" --output ./artifacts
echo "::endgroup::"
fi

echo "Generated artifacts:"
ls -la ./artifacts

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: codout-framework-all-nupkg
path: ./artifacts/*.nupkg
if-no-files-found: error

- name: Push to NuGet.org
if: inputs.dry_run != true
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
set -euo pipefail
if [ -z "${NUGET_API_KEY:-}" ]; then
echo "::error::NUGET_API_KEY secret is not set; aborting."
exit 1
fi
# --skip-duplicate makes this idempotent: re-runs will not fail on
# already-published versions.
dotnet nuget push "./artifacts/*.nupkg" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

- name: Summary
if: always()
run: |
{
echo "## Mass release summary"
echo ""
echo "- Dry run: \`${{ inputs.dry_run }}\`"
echo "- Mcp included: \`${{ inputs.include_mcp }}\`"
echo ""
echo "### Packed artifacts"
echo ""
echo '```'
ls -1 ./artifacts 2>/dev/null || echo "(no artifacts directory)"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
170 changes: 170 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
name: Package Release

# Generic release workflow for the Codout.Framework packages, one package at a
# time. Mapping from short name to .csproj lives in .github/release-packages.json.
#
# Trigger by pushing a tag in the form "<short>-v<version>", e.g. "ef-v6.3.0",
# "mongo-v6.2.3", "mailer-razor-v6.2.2". The Codout.Framework.Mcp package keeps
# its own dedicated workflow (mcp-release.yml), so "mcp-v*" tags are excluded
# here to avoid a double release.
#
# workflow_dispatch builds and packs without publishing, useful for smoke-
# testing a package locally before tagging.

on:
push:
tags:
- '*-v*.*.*'
- '!mcp-v*'
workflow_dispatch:
inputs:
package:
description: 'Package short name (see .github/release-packages.json).'
type: string
required: true
version:
description: 'Optional version override (e.g. 6.3.1). Leave empty to use the <Version> from the .csproj.'
required: false

env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
CONFIGURATION: Release
MAPPING: .github/release-packages.json

jobs:
build-pack:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
package: ${{ steps.resolve.outputs.package }}
project: ${{ steps.resolve.outputs.project }}
version: ${{ steps.resolve.outputs.version }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Full history so the master-ancestry gate can resolve merge-base.
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Verify tag is on master
if: startsWith(github.ref, 'refs/tags/')
run: |
set -euo pipefail
git fetch origin master --quiet
if ! git merge-base --is-ancestor HEAD origin/master; then
echo "::error::Tag '${GITHUB_REF_NAME}' is not an ancestor of origin/master."
echo "Releases must be cut from commits that have been merged into master."
exit 1
fi
echo "Tag '${GITHUB_REF_NAME}' is on master: OK"

- name: Resolve package and version
id: resolve
run: |
set -euo pipefail
if [ -n "${{ github.event.inputs.package }}" ]; then
PKG="${{ github.event.inputs.package }}"
VERSION="${{ github.event.inputs.version }}"
else
TAG="${GITHUB_REF#refs/tags/}"
PKG="${TAG%-v*}"
VERSION="${TAG##*-v}"
fi

PROJECT=$(jq -r --arg p "$PKG" '.[$p] // empty' "$MAPPING")
if [ -z "$PROJECT" ]; then
echo "::error::Unknown package '$PKG'."
echo "Known packages:"
jq -r 'keys[]' "$MAPPING" | sed 's/^/ - /'
exit 1
fi
if [ ! -f "$PROJECT" ]; then
echo "::error::Mapped project not found on disk: $PROJECT"
exit 1
fi

echo "package=$PKG" | tee -a "$GITHUB_OUTPUT"
echo "project=$PROJECT" | tee -a "$GITHUB_OUTPUT"
echo "version=$VERSION" | tee -a "$GITHUB_OUTPUT"

# Build + test the whole solution as a safety net. The solution currently
# has no tests registered in it (tests live in side projects with external
# infra dependencies, see appsettings.json), so `dotnet test` here is a
# no-op today and will pick up tests automatically once they are added.
- name: Restore (solution)
run: dotnet restore Codout.Framework.sln

- name: Build (solution)
run: dotnet build Codout.Framework.sln --configuration "$CONFIGURATION" --no-restore

- name: Test (solution)
run: dotnet test Codout.Framework.sln --configuration "$CONFIGURATION" --no-build --verbosity normal

- name: Pack
run: |
set -euo pipefail
VERSION="${{ steps.resolve.outputs.version }}"
PROJECT="${{ steps.resolve.outputs.project }}"
# Pack performs its own restore/build for the target. Some packages
# (Cosmos, DocumentDB, etc.) are not part of Codout.Framework.sln, so
# we deliberately do NOT pass --no-build here.
if [ -n "$VERSION" ]; then
dotnet pack "$PROJECT" --configuration "$CONFIGURATION" \
-p:Version="$VERSION" \
-p:PackageVersion="$VERSION" \
--output ./artifacts
else
dotnet pack "$PROJECT" --configuration "$CONFIGURATION" \
--output ./artifacts
fi
ls -la ./artifacts

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ steps.resolve.outputs.package }}-nupkg
path: ./artifacts/*.nupkg
if-no-files-found: error

publish:
needs: build-pack
runs-on: ubuntu-latest
# Only push to NuGet for real release tags. workflow_dispatch (manual) runs
# are build/pack-only smoke tests.
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: read

steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: ${{ needs.build-pack.outputs.package }}-nupkg
path: ./artifacts

- name: Push to NuGet.org
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
set -euo pipefail
if [ -z "${NUGET_API_KEY:-}" ]; then
echo "::error::NUGET_API_KEY secret is not set; aborting."
exit 1
fi
dotnet nuget push "./artifacts/*.nupkg" \
--api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
Loading
Loading