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"]
  1. Checkout — Uses actions/checkout@v5 to clone the repository.
  2. Setup .NET SDK — Uses actions/setup-dotnet@v5, reading the SDK version from global.json (currently .NET SDK 10.0.101 with latestFeature roll-forward). Enables NuGet package caching based on packages.lock.json files.
  3. Quality — Runs ./build/quality.ps1, which orchestrates the full quality pipeline.
  4. Pack — Runs ./build/pack.ps1 -NoRestore to 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:

  1. Tag push — Any tag matching the v* pattern (e.g., v1.0.0, v2.1.0-preview.1).
  2. Manual dispatch — Via workflow_dispatch with a required version input 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:

  1. Required files — Verifies that essential repository files exist (Manifold.slnx, README.md, LICENSE, build/pack.ps1, .github/workflows/ci.yml).
  2. Forbidden references — Scans all .cs, .csproj, .props, and .targets files under src/, tests/, and samples/ for references to legacy namespace prefixes (DalamudMCP.*).

Sources: build/architecture.ps1:7-48

Related Pages