CI CD and Release Pipeline - Garume/Manifold GitHub Wiki
CI/CD and Release Pipeline
Manifold uses two GitHub Actions workflows to automate quality assurance and package distribution. The ci workflow runs on every push and pull request, executing the full quality gate (restore, build, format check, test, and architecture validation). The publish workflow triggers on version tags (v*) or manual dispatch, running the same quality gate before packing and publishing NuGet packages to nuget.org and creating a GitHub Release.
This page covers the complete automation pipeline from code push through package publication. For details on the individual build scripts invoked by these workflows, see Build System and Scripts. For test infrastructure, see Testing Strategy. For architecture validation rules, see Architecture Validation and Code Quality.
Workflow Overview
The repository contains two workflow definitions under .github/workflows/:
| Workflow | File | Trigger | Purpose |
|---|---|---|---|
| ci | ci.yml |
Every push and pull request | Quality gate: build, format, test, architecture checks, and trial pack |
| publish | publish.yml |
Tag push (v*) or manual dispatch |
Quality gate + NuGet publish + GitHub Release |
Sources: .github/workflows/ci.yml:1-24, .github/workflows/publish.yml:1-8
flowchart TD
A[Push / PR] -->|ci.yml| B[ci workflow]
C[Tag v* push] -->|publish.yml| D[publish workflow]
E[Manual dispatch] -->|publish.yml| D
B --> F[Quality Gate]
B --> G[Trial Pack]
D --> H[Resolve Version]
H --> I[Quality Gate]
I --> J[Pack with Version]
J --> K[NuGet Publish]
K --> L[GitHub Release]
CI Workflow — ci.yml
The CI workflow runs on every push and pull request to any branch. It consists of a single job, quality-and-test, that executes on windows-latest.
Job Steps
flowchart TD
A[Checkout] --> B[Setup .NET SDK]
B --> C["quality.ps1"]
C --> D["pack.ps1 -NoRestore"]
- Checkout — Uses
actions/checkout@v5to clone the repository. - Setup .NET SDK — Uses
actions/setup-dotnet@v5, reading the SDK version fromglobal.json(currently .NET SDK10.0.101withlatestFeatureroll-forward). Enables NuGet package caching based onpackages.lock.jsonfiles. - Quality — Runs
./build/quality.ps1, which orchestrates the full quality pipeline. - Pack — Runs
./build/pack.ps1 -NoRestoreto verify that NuGet packages can be produced successfully.
Sources: .github/workflows/ci.yml:7-24, global.json:1-9
Quality Gate Pipeline
The quality.ps1 script chains five sub-scripts in strict order. Any failure halts the pipeline immediately.
flowchart TD
A["restore.ps1"] --> B["build.ps1"]
B --> C["format.ps1"]
C --> D["test.ps1"]
D --> E["architecture.ps1"]
| Step | Script | Action |
|---|---|---|
| 1 | restore.ps1 |
dotnet restore Manifold.slnx |
| 2 | build.ps1 |
Builds all src/ and samples/ projects individually |
| 3 | format.ps1 |
dotnet format --verify-no-changes (enforces EditorConfig) |
| 4 | test.ps1 |
Runs all tests/ projects via dotnet test |
| 5 | architecture.ps1 |
Validates required files and forbidden namespace references |
Sources: build/quality.ps1:1-35, build/restore.ps1:1-22, build/build.ps1:1-37, build/format.ps1:1-30, build/test.ps1:1-49, build/architecture.ps1:1-50
SDK Resolution
All build scripts use Get-DotNetCommand to locate the .NET CLI. This helper checks for a local .dotnet/dotnet.exe in the repository root first, then falls back to the system-installed dotnet command.
function Get-DotNetCommand {
param([string]$RepositoryRoot)
$localDotNet = Join-Path $RepositoryRoot '.dotnet\dotnet.exe'
if (Test-Path $localDotNet) {
return $localDotNet
}
$command = Get-Command dotnet -ErrorAction Stop
return $command.Source
}
Sources: build/Get-DotNetCommand.ps1:1-16
NuGet Package Caching
Both workflows configure dependency caching through actions/setup-dotnet@v5. The cache key is derived from all packages.lock.json files in the repository, which are enabled via the RestorePackagesWithLockFile property in Directory.Build.props.
Sources: .github/workflows/ci.yml:13-17, Directory.Build.props:16
Publish Workflow — publish.yml
The publish workflow handles the full release cycle: version resolution, quality validation, package creation, NuGet publication, and GitHub Release creation.
Triggers
The workflow activates on two events:
- Tag push — Any tag matching the
v*pattern (e.g.,v1.0.0,v2.1.0-preview.1). - Manual dispatch — Via
workflow_dispatchwith a requiredversioninput string (e.g.,1.0.0).
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: Package version to publish, for example 1.0.0
required: true
type: string
Sources: .github/workflows/publish.yml:3-12
Permissions
The publish job requests two elevated permissions:
| Permission | Purpose |
|---|---|
contents: write |
Required to create GitHub Releases and upload assets |
id-token: write |
Required for trusted publishing via NuGet/login@v1 |
Sources: .github/workflows/publish.yml:16-19
Job Steps
flowchart TD
A[Checkout] --> B[Setup .NET SDK]
B --> C[Resolve Version]
C --> D["quality.ps1"]
D --> E["pack.ps1 -PackageVersion"]
E --> F["NuGet/login@v1"]
F --> G[Push .nupkg files]
G --> H{Tag push?}
H -->|Yes| I[Create GitHub Release]
H -->|No| J[Done]
Sources: .github/workflows/publish.yml:14-101
Version Resolution
The version resolution step determines the package version from either the manual input or the tag name, stripping the v prefix if present:
$packageVersion = "${{ github.event.inputs.version }}"
if ([string]::IsNullOrWhiteSpace($packageVersion)) {
$packageVersion = "${{ github.ref_name }}"
}
if ($packageVersion.StartsWith('v')) {
$packageVersion = $packageVersion.Substring(1)
}
The resolved version is stored in the PACKAGE_VERSION environment variable for use by subsequent steps.
Sources: .github/workflows/publish.yml:30-47
Package Creation
The pack.ps1 script reads the solution file (Manifold.slnx) to discover all projects under src/ and runs dotnet pack on each in Release configuration. When a PackageVersion parameter is provided, it overrides the default version via the MSBuild property -p:PackageVersion.
All .nupkg files are output to .artifacts/packages/. Each package includes symbol packages (.snupkg) for source-level debugging.
$arguments = @('pack', $packProject, '-c', 'Release',
"-p:PackageOutputPath=$packageOutput")
if (-not [string]::IsNullOrWhiteSpace($PackageVersion)) {
$arguments += "-p:PackageVersion=$PackageVersion"
}
Sources: build/pack.ps1:22-51
Published Packages
The four source packages correspond to the four projects under src/:
| Package | Description |
|---|---|
Manifold |
Core operation contracts, descriptors, and binding primitives |
Manifold.Cli |
CLI binding and fast invocation runtime |
Manifold.Generators |
Incremental source generator |
Manifold.Mcp |
MCP tool binding and invocation runtime |
Each package includes the repository README.md and LICENSE file, configured in Directory.Build.targets for all packable projects.
Sources: src/Manifold/Manifold.csproj:1-19, src/Manifold.Cli/Manifold.Cli.csproj:1-17, Directory.Build.targets:1-11
NuGet Authentication
The workflow uses the NuGet/login@v1 action for trusted publishing, which avoids storing long-lived API keys. The user is resolved from the NUGET_USER repository variable if set, falling back to the repository owner.
- name: NuGet login
uses: NuGet/login@v1
id: login
with:
user: ${{ vars.NUGET_USER != '' && vars.NUGET_USER || github.repository_owner }}
Sources: .github/workflows/publish.yml:56-61
Package Publishing
The publish step enumerates all .nupkg files (excluding .snupkg symbol packages) from the artifacts directory and pushes each to nuget.org. The --skip-duplicate flag prevents failures when re-publishing an existing version.
$packages = Get-ChildItem .\.artifacts\packages\*.nupkg |
Where-Object { $_.Name -notlike '*.snupkg' } |
Sort-Object Name
foreach ($package in $packages) {
dotnet nuget push $package.FullName `
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" `
--source https://api.nuget.org/v3/index.json `
--skip-duplicate
}
Sources: .github/workflows/publish.yml:63-79
GitHub Release Creation
When triggered by a tag push (not manual dispatch), the workflow creates a GitHub Release using the gh CLI. The release is titled with the tag name, uses auto-generated release notes, and attaches all .nupkg files as assets.
gh release create $tag `
--repo "${{ github.repository }}" `
--title $tag `
--generate-notes `
$assets
This step is skipped for workflow_dispatch triggers, as indicated by the condition if: github.event_name == 'push'.
Sources: .github/workflows/publish.yml:80-101
Full Release Flow
The following sequence diagram illustrates the complete release process from tagging through package availability on nuget.org:
sequenceDiagram
participant Dev as Developer
participant GH as GitHub
participant CI as Actions Runner
participant NU as nuget.org
Dev->>GH: git push tag v1.0.0
GH->>CI: Trigger publish.yml
CI->>CI: Resolve version (1.0.0)
CI->>CI: restore.ps1
CI->>CI: build.ps1
CI->>CI: format.ps1
CI->>CI: test.ps1
CI->>CI: architecture.ps1
CI->>CI: pack.ps1 -PackageVersion 1.0.0
CI->>NU: NuGet/login (trusted publishing)
CI->>NU: dotnet nuget push (4 packages)
CI->>GH: gh release create v1.0.0
GH-->>Dev: Release with .nupkg assets
NU-->>Dev: Packages available on nuget.org
Build Configuration
MSBuild Properties
Key build properties applied globally through Directory.Build.props affect CI behavior:
| Property | Value | Effect |
|---|---|---|
TreatWarningsAsErrors |
true |
All warnings fail the build |
EnforceCodeStyleInBuild |
true |
Style violations are build errors |
AnalysisLevel |
latest-recommended |
Latest .NET analyzer rules |
Deterministic |
true |
Reproducible builds |
RestorePackagesWithLockFile |
true |
Enables lock file for CI caching |
IncludeSymbols |
true |
Produces .snupkg alongside .nupkg |
Sources: Directory.Build.props:1-16
Package Metadata
All source packages share common metadata defined in their .csproj files:
| Field | Value |
|---|---|
| Authors | Garume |
| License | MIT |
| Repository | https://github.com/Garume/Manifold |
| Symbol format | snupkg |
Sources: src/Manifold/Manifold.csproj:7-18
Architecture Validation in CI
The architecture.ps1 script runs as the final quality check and enforces two categories of rules:
- Required files — Verifies that essential repository files exist (
Manifold.slnx,README.md,LICENSE,build/pack.ps1,.github/workflows/ci.yml). - Forbidden references — Scans all
.cs,.csproj,.props, and.targetsfiles undersrc/,tests/, andsamples/for references to legacy namespace prefixes (DalamudMCP.*).
Sources: build/architecture.ps1:7-48
Related Pages
- Build System and Scripts — Detailed documentation of the PowerShell build scripts invoked by both workflows
- Testing Strategy — Test infrastructure, xUnit v3 configuration, and coverage thresholds
- Architecture Validation and Code Quality — Architecture checks, code style enforcement, and analyzer configuration
- Project Structure and Tech Stack — Repository layout, projects, and tooling
- Architecture Overview — High-level system architecture and package relationships