This runbook is the source of truth for publishing RolandUI.DevScope to nuget.org and GitHub Packages, and for creating the matching GitHub Release. It adopts AvaScope's release-gate principle, but it matches DevScope's simpler release workflow: publishing a GitHub Release triggers .github/workflows/nuget-publish.yml.
Publishing a GitHub Release is the irreversible step. Complete every preparation and validation step before changing a draft release to published.
Every release must have:
- a SemVer package version and matching
v<version>Git tag; - a GitHub release-tracking issue with locked scope;
- all in-scope issues closed or explicitly deferred;
- a clean, pushed
maincommit that passed the full release gate; - reviewed release notes;
- a successful GitHub Actions release run;
- matching immutable packages on nuget.org and GitHub Packages, plus GitHub Release assets.
Use prerelease versions such as 0.1.0-preview.1 until the public API is ready for a stable release. Never reuse a version that has reached either package registry.
DevScope uses NuGet Trusted Publishing for nuget.org. It does not store a long-lived NuGet API key in GitHub. The workflow requests a GitHub OIDC token, and NuGet/login@v1 exchanges that token once for a short-lived NuGet API key immediately before package publication.
Before the first release, create this policy under the RolandUI account on the nuget.org Trusted Publishing page:
| Field | Value |
|---|---|
| Policy Name | DevScope GitHub Release |
| Package Owner | RolandUI |
| Repository Owner | RolandUI |
| Repository | DevScope |
| Workflow File | nuget-publish.yml |
| Environment | Leave empty |
The workflow file field contains only the filename, not .github/workflows/nuget-publish.yml. Leave Environment empty because the release job does not declare a GitHub Actions environment.
The repository workflow must retain all three permissions:
permissions:
contents: write
id-token: write
packages: writecontents: write allows release asset uploads. id-token: write allows GitHub to issue the OIDC token used by Trusted Publishing. packages: write allows the workflow's built-in GITHUB_TOKEN to publish to GitHub Packages. No NUGET_API_KEY or GitHub Packages repository secret is required.
The GitHub Packages NuGet registry is https://nuget.pkg.github.com/RolandUI/index.json. The package's RepositoryUrl links it to RolandUI/DevScope; a package first created by this workflow should inherit the public repository's visibility and grant this repository Actions access. After the first publication, verify the repository link, public visibility, and Actions access on the package settings page. If the organization disables automatic permission inheritance, configure those settings explicitly before the next release. Keep nuget.org as the primary consumer feed; GitHub Packages is the repository-integrated secondary registry.
Create one release-tracking issue and record:
- target version and release type (
preview, patch, minor, or major); - included issues and explicit deferrals;
- compatibility targets (
net8.0,net10.0, and Avalonia version); - required documentation changes;
- validation results and final release commit.
Use variables for the remaining commands:
$version = "<version>" # example: 0.1.0-preview.1
$tag = "v$version"
$repo = "RolandUI/DevScope"Check that neither the tag nor a GitHub Release already exists:
git ls-remote --tags origin "refs/tags/$tag"
gh release view $tag --repo $repogit ls-remote must return no matching tag, and gh release view must report that the release was not found. The non-zero gh release view exit is expected at this stage.
Work from the exact commit that will be tagged:
git switch main
git pull --ff-only origin main
git status --short --branchThe worktree must be clean, main must match origin/main, and all release-scope commits must already be pushed. The final release candidate must not contain unfinished feature work.
Review the release-facing metadata:
- package ID:
RolandUI.DevScope; - project and repository URLs:
https://github.com/RolandUI/DevScope; - GitHub Packages registry:
https://nuget.pkg.github.com/RolandUI/index.json; - license and preserved attribution;
- README installation and compatibility text;
- release notes, including breaking changes and known limitations.
Restore once, then validate both configurations and both target frameworks:
dotnet restore DevScope.slnx
dotnet test DevScope.slnx --configuration Debug --no-restore
dotnet test DevScope.slnx --configuration Release --no-restore
dotnet build src/DevScope.csproj --configuration Release --no-restore
git diff --checkCreate the same package shape as GitHub Actions, using the intended version:
$out = Join-Path $env:TEMP "DevScope-release-$version-$([guid]::NewGuid().ToString('N'))"
dotnet pack src/DevScope.csproj `
--configuration Release `
--no-build `
--no-restore `
-p:PackageVersion=$version `
-p:IncludeSymbols=true `
-p:SymbolPackageFormat=snupkg `
--output $outThe directory must contain exactly one .nupkg and its .snupkg. Inspect the primary package:
$package = Get-Item (Join-Path $out "RolandUI.DevScope.$version.nupkg")
Get-FileHash $package.FullName -Algorithm SHA256
tar -tf $package.FullName
tar -xOf $package.FullName RolandUI.DevScope.nuspecVerify at minimum:
- package ID and version are
RolandUI.DevScopeand$version; - repository URL and commit point to the release candidate;
- dependencies use the intended Avalonia version;
README.mdis present;lib/net8.0/RolandUI.DevScope.dllandlib/net10.0/RolandUI.DevScope.dllare present;- no unexpected files or legacy project identifiers are present.
Record the commands, test counts, package SHA-256, and release candidate commit in the release issue. Do not continue if any gate fails.
Tag only the validated commit, then create a draft release:
git tag -a $tag -m "DevScope $version"
git push origin "refs/tags/$tag"
gh release create $tag `
--repo $repo `
--draft `
--verify-tag `
--title "DevScope $version" `
--generate-notesBefore publishing the draft, verify:
- the tag resolves to the recorded release candidate commit;
- the title, release notes, compatibility information, and known limitations are correct;
- the nuget.org Trusted Publishing policy is active and exactly matches the repository and workflow fields above;
- there is no existing package version with the same number on nuget.org or GitHub Packages.
Publishing the draft starts the release workflow:
gh release edit $tag --repo $repo --draft=false --latestDo not run this command without explicit release authorization.
The Publish NuGet Package workflow will:
- check out the tagged source;
- restore and build
src/DevScope.csprojin Release mode; - pack
RolandUI.DevScopeusing the tag as the package version; - attach
.nupkgand.snupkgfiles to the GitHub Release; - exchange the GitHub OIDC token for a short-lived NuGet credential;
- push the package and symbols to nuget.org;
- authenticate with the workflow
GITHUB_TOKENand push the primary.nupkgto GitHub Packages.
Find and watch the run:
gh run list --repo $repo --workflow nuget-publish.yml --limit 5
gh run watch <run-id> --repo $repo --exit-statusIf GitHub records the published release event but no run is created, use the workflow's tag-bound manual recovery trigger. It validates that the release is already published and checks out the immutable release tag rather than main:
gh workflow run nuget-publish.yml --repo $repo --ref main -f tag=$tagDo not use the manual trigger before the matching GitHub Release is published, and never use it to publish an untagged commit.
If the run fails, inspect it before retrying:
gh run view <run-id> --repo $repo --log-failedConfirm the tag, release assets, nuget.org registration, and GitHub Packages registration:
git ls-remote --tags origin "refs/tags/$tag"
gh release view $tag --repo $repo --json url,tagName,isDraft,assets
$versions = Invoke-RestMethod "https://api.nuget.org/v3-flatcontainer/rolandui.devscope/index.json"
$versions.versions -contains $version
gh auth refresh --hostname github.com --scopes read:packages
$githubVersion = gh api --paginate "/users/RolandUI/packages/nuget/RolandUI.DevScope/versions" --jq ".[] | select(.name == `"$version`") | .name"
$githubVersion -eq $versionBoth registry expressions must return True. Registry indexing can take a few minutes; retry verification before treating a successful workflow as failed. The gh account used for verification must have the read:packages scope.
Install the exact published version in a disposable sample or real consumer application and confirm that it restores for both supported target frameworks. Then update the release issue with:
- GitHub Release URL;
- workflow run URL;
- tag and commit hash;
- nuget.org and GitHub Packages versions;
- published package SHA-256;
- smoke-test result.
Close the release issue only after all publication checks pass.
- If publication fails before either registry accepts the package, correct the Trusted Publishing policy, token permission, or workflow problem and rerun the failed workflow. Both pushes use
--skip-duplicate, so a partial retry is safe. - If nuget.org succeeds but GitHub Packages fails, fix the GitHub package visibility or Actions access and rerun the workflow. The immutable nuget.org version will be skipped and the GitHub Packages push will be retried.
- If the
release: publishedevent is recorded but does not create a run, use the documentedworkflow_dispatchrecovery with the existing immutable release tag. - If either registry accepted the package, treat its version as immutable. Never overwrite, delete, move, or reuse that version; publish a new patch or prerelease version for corrections.
- If a bad package reaches a registry, unlist or delete it only when appropriate and create a follow-up release. Deleting the GitHub Release does not remove either registry package.
- Do not move or recreate a public release tag after publication.
- Do not publish manually from a developer machine while the GitHub workflow is the documented release path.