From 53be3ee001fc1275d92f222b9412e10b9aa115ef Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Fri, 29 May 2026 20:06:49 +1200 Subject: [PATCH] ci(release): nuget.org publish is opt-in; GitHub Packages is the v11 default channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the v11-off-nuget direction. Tag pushes on release/v11 no longer auto-publish to nuget.org — they publish to GitHub Packages + GitHub Releases only. To publish Fallout.* to nuget.org you must invoke workflow_dispatch with `publish-to-nugetorg=true`, an explicit "this release is stabilised enough for nuget.org" switch. This *modifies* the routing originally documented in milestone #13's release.yml refactor (#288). The release-branch model, multi-channel fan-out, and CD gating are all unchanged — only the routing policy shifts. ## Workflow changes (`.github/workflows/release.yml`) - New `workflow_dispatch` input `publish-to-nugetorg` (boolean, default false). - `publish-nuget-org` job gains `if:` condition requiring both `github.event_name == 'workflow_dispatch'` and `inputs.publish-to-nugetorg == true`. Tag pushes skip this job. - `publish-github-packages` job changes its glob from `Nuke.*.nupkg` to `*.nupkg` — now publishes both Fallout.* and Nuke.* to GH Packages. GH Packages is the v11 release channel; Nuke.* shim routing per #47 is preserved (still goes here permanently). - Header comment + per-job comments rewritten to describe the new routing policy explicitly. Three layers of safety on the nuget.org path now: 1. Tag protection ruleset (only repo admins can create v* tags). 2. workflow_dispatch flag opt-in. 3. nuget-org env approval gate. ## Doc updates - `docs/agents/release-and-versioning.md` — Release pipeline section rewritten. Table updated. New "Why nuget.org is opt-in for v11" subsection. workflow_dispatch inputs documented. - `docs/branching-and-release.md` — Channel taxonomy table extended with "Current v11 use" column. "Cutting a release" split into two sub-runbooks: routine (GitHub Packages only) and stabilised (with the opt-in flag). - `docs/adr/0002-v11-off-nuget-by-default.md` — new ADR capturing the decision and three rejected alternatives. ## CHANGELOG Entry under [Unreleased] — 11.0 → Process explicitly noting this *modifies* the routing originally documented in the milestone #13 umbrella entry (rather than backdating that entry; preserves history of how the decision evolved). ## Out of scope - Unlisting existing 11.0.x on nuget.org — separate work (see worktree `unlist-10.3` for the parallel 10.3 unlisting). - When/how v11 transitions to "stabilised" and opts into nuget.org by default — a future ADR / decision when that moment arrives. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 73 +++++++++++------ CHANGELOG.md | 2 + docs/adr/0002-v11-off-nuget-by-default.md | 95 +++++++++++++++++++++++ docs/agents/release-and-versioning.md | 39 +++++++--- docs/branching-and-release.md | 62 ++++++++++----- 5 files changed, 217 insertions(+), 54 deletions(-) create mode 100644 docs/adr/0002-v11-off-nuget-by-default.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 651106690..12e6ce31f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,20 +2,30 @@ # rationale (lets us bridge GitHub's NUGET_API_KEY secret to Fallout's # NuGetApiKey parameter, which would otherwise have to share a name). # -# Shape (post #274, milestone #13): +# Shape: # - Trigger: tag push `v*` on a `release/v*` branch. Tag protection ruleset # restricts who can create those tags to repo admins. # - Optional fallback: workflow_dispatch with an existing-tag input, for -# re-runs after a transient publish-API failure. +# re-runs after a transient publish-API failure, OR to opt into a +# nuget.org publish (see below). # - validate-ref → test-and-pack → fan out to three publish jobs in -# parallel, one per GitHub Environment / Tier: -# - publish-nuget-org (Tier 1, approval-gated, Fallout.* only) -# - publish-github-packages (Tier 2, Nuke.* transition shims) +# parallel: +# - publish-nuget-org (Tier 1, opt-in via workflow_dispatch flag, approval-gated) +# - publish-github-packages (Tier 2, all packages — Fallout.* + Nuke.*) # - publish-github-releases (artifact bundling on the tag's GH Release) # -# Tier philosophy (RFC #267): nuget.org = production-grade & slow, -# GitHub Packages = bleeding edge / beta, GitHub Releases = bundled artifacts. -# Channel decisions encoded in the per-job `dotnet nuget push` glob patterns. +# Channel routing for v11+ (current direction): +# - nuget.org is reserved for v10.x and (eventually) "stabilised" v11. Tag +# pushes do NOT auto-publish to nuget.org. To publish Fallout.* to +# nuget.org you must invoke workflow_dispatch with publish-to-nugetorg=true +# — a conscious "this release is stabilised enough for nuget.org" switch. +# - GitHub Packages gets every Fallout.* and Nuke.* build via tag push. +# This is the de-facto release channel during v11 prerelease/stabilisation. +# - GitHub Releases bundles nupkgs on the tag's release page regardless. +# +# When v11 stabilises (or for cutting v10.x maintenance patches), use the +# workflow_dispatch path with publish-to-nugetorg=true. See +# docs/branching-and-release.md for the full runbook. name: release @@ -23,14 +33,23 @@ on: push: tags: - 'v*' - # Fallback for re-running a partial publish after a transient API failure. - # Pick an existing tag — the workflow checks it out and re-runs the publish - # fan-out. `--skip-duplicate` on each push makes re-runs idempotent. + # Manual fallback for two scenarios: + # 1. Re-running a partial publish after a transient API failure (pick the + # tag, leave publish-to-nugetorg at its default). + # 2. Opting into a nuget.org publish for a stabilised release (pick the + # tag, set publish-to-nugetorg to true). Still gated by the nuget-org + # env approval before the actual push. + # `--skip-duplicate` on each push makes re-runs idempotent. workflow_dispatch: inputs: tag: description: 'Existing tag to (re-)release (e.g. v11.0.5)' required: true + publish-to-nugetorg: + description: 'Publish Fallout.* to nuget.org? Default false — opt-in for stabilised releases only.' + required: false + type: boolean + default: false permissions: contents: read @@ -92,13 +111,19 @@ jobs: retention-days: 7 if-no-files-found: error - # Tier 1 — production. Approval-gated via the nuget-org GitHub Environment. - # Only Fallout.* packages go here; Nuke.* shim IDs are owned by the original - # NUKE maintainer on nuget.org and are routed to GitHub Packages instead. + # Tier 1 — production / nuget.org. **OPT-IN ONLY.** Tag pushes do NOT trigger + # this job; you must invoke workflow_dispatch with publish-to-nugetorg=true. + # The env approval gate on `nuget-org` is the second layer — even after opting + # in via the input flag, you still approve the deployment. + # + # The opt-in defaults to false because v11 is currently published to GitHub + # Packages only (see header comment). When v11 "stabilises" — or for cutting + # a v10.x maintenance patch — invoke workflow_dispatch with the flag set. publish-nuget-org: name: publish → nuget.org runs-on: ubuntu-latest needs: [test-and-pack] + if: github.event_name == 'workflow_dispatch' && inputs.publish-to-nugetorg == true environment: name: nuget-org url: https://www.nuget.org/profiles/Fallout @@ -129,9 +154,11 @@ jobs: --skip-duplicate done - # Tier 2 — bleeding edge / beta. The Nuke.* transition shims live here per - # #47 (we don't own the Nuke.* IDs on nuget.org). Future-direction: also - # publish Fallout.* beta builds here on a faster cadence than nuget.org. + # Tier 2 — GitHub Packages. The de-facto release channel for v11. Pushes + # both Fallout.* AND Nuke.* transition shims. The Nuke.* shim IDs are owned + # by the original NUKE maintainer on nuget.org (#47) — GitHub Packages is + # their permanent home. Fallout.* is here because v11 is "off nuget.org" by + # default (see header). publish-github-packages: name: publish → GitHub Packages runs-on: ubuntu-latest @@ -151,16 +178,16 @@ jobs: with: name: packages path: output/packages - - name: 'Push: Nuke.* transition shims to GitHub Packages' + - name: 'Push: all *.nupkg to GitHub Packages' run: | set -euo pipefail shopt -s nullglob - shims=(output/packages/Nuke.*.nupkg) - if [ ${#shims[@]} -eq 0 ]; then - echo "No Nuke.* shim packages found — skipping." - exit 0 + packages=(output/packages/*.nupkg) + if [ ${#packages[@]} -eq 0 ]; then + echo "::error::No packages found in artifact — nothing to publish." + exit 1 fi - for pkg in "${shims[@]}"; do + for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages..." dotnet nuget push "$pkg" \ --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c708f41b..d7c34fe0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - **`main`'s `version.json` major** stays at 11 for now ([#271](https://github.com/ChrisonSimtian/Fallout/issues/271) deferred until v12 work begins). - **Tier 3 Docker local NuGet server** ([#279](https://github.com/ChrisonSimtian/Fallout/issues/279)) — pre-merge testing channel — landed as a separate work item; initial setup in PR [#287](https://github.com/ChrisonSimtian/Fallout/pull/287). +- **nuget.org publish is now opt-in for v11; GitHub Packages is the default release channel.** Tag pushes on `release/v11` publish to GitHub Packages + GitHub Releases only; the `publish-nuget-org` job is skipped unless `workflow_dispatch` is invoked with `publish-to-nugetorg=true` (and the existing `nuget-org` env approval gate still fires). `publish-github-packages` now pushes **all** `*.nupkg` (Fallout.* + Nuke.*), not just Nuke.* shims — GitHub Packages is the de-facto v11 release channel during stabilisation. nuget.org is reserved for v10.x maintenance lines and a future stabilised v11. Decision recorded in [`docs/adr/0002-v11-off-nuget-by-default.md`](docs/adr/0002-v11-off-nuget-by-default.md); maintainer runbook updated in [`docs/branching-and-release.md`](docs/branching-and-release.md). This *modifies* the routing originally documented in the milestone #13 umbrella entry above — the branching model and CD shape are unchanged, only the routing policy. + ## [10.2.0] / 2026-05-21 The NUKE → Fallout hard fork. Originally NUKE by [@matkoch](https://github.com/matkoch) and contributors; under new maintenance and rebranded to Fallout. diff --git a/docs/adr/0002-v11-off-nuget-by-default.md b/docs/adr/0002-v11-off-nuget-by-default.md new file mode 100644 index 000000000..955adc108 --- /dev/null +++ b/docs/adr/0002-v11-off-nuget-by-default.md @@ -0,0 +1,95 @@ +# ADR-0002: v11 publishes to GitHub Packages by default; nuget.org is opt-in + +## Status + +Accepted (2026-05-29). + +## Context + +[ADR-0001](0001-release-branch-model.md) established the tag-triggered, multi-channel CD pipeline. The original routing was: + +- Tag on `release/v11` → publish Fallout.* to nuget.org (Tier 1) + publish Nuke.* shims to GitHub Packages (Tier 2) + attach all nupkgs to GitHub Release. + +Shortly after that landed, Chris flagged a separate direction: **v11 should not go to nuget.org by default.** nuget.org is reserved for v10.x maintenance lines and a future "stabilised" v11. Existing `11.0.x` releases on nuget.org are being unlisted in parallel. + +Reasons (not exhaustive — Chris's call): + +- The v11 rebrand surface is still settling. Public-API consumers depending on Fallout.* via nuget.org get exposed to churn that hasn't fully stabilised. +- 10.x is still the de-facto stable line for most consumers; the project-controlled nuget.org space should remain v10.x-oriented until v11 has shipped a stable enough surface to assume the position. +- GitHub Packages is a fine release channel for beta-and-stabilising work — opt-in consumers add the feed to their `nuget.config`, the broader consumer base isn't dragged into rebrand churn. + +The pipeline shipped in ADR-0001 routed Fallout.* to nuget.org on every tag — exactly the wrong default for the current v11 state. + +## Decision + +Make nuget.org publish **opt-in via a `workflow_dispatch` input flag**. Specifically: + +- Add `publish-to-nugetorg` (boolean, default `false`) to the `release.yml` `workflow_dispatch` inputs. +- `publish-nuget-org` job gains `if: github.event_name == 'workflow_dispatch' && inputs.publish-to-nugetorg == true`. +- Tag pushes alone do NOT trigger nuget.org publish. They publish to GitHub Packages + GitHub Releases only. +- To publish Fallout.* to nuget.org, invoke `workflow_dispatch` with `publish-to-nugetorg=true`. The `nuget-org` env approval gate still fires before the actual push — two layers of intent. + +Companion change: **`publish-github-packages` now pushes all `*.nupkg`**, not just `Nuke.*`. GitHub Packages is the v11 release channel; every tag publishes the full set of Fallout.* + Nuke.* packages there. + +The Nuke.* shim packages always go to GitHub Packages (per [#47](https://github.com/ChrisonSimtian/Fallout/issues/47) — those IDs are owned by the original NUKE maintainer on nuget.org). This is unchanged. + +## Consequences + +### Positive + +- **Default safety.** A tagged release on `release/v11` can't accidentally publish to nuget.org. The opt-in flag is the explicit "this is stabilised enough" decision. +- **Three layers of nuget.org safety**: tag protection (admin only) + flag opt-in + env approval. Reflects the irreversibility of nuget.org publishes. +- **No CI restructuring beyond the flag.** The existing job shape, environment routing, and approval gates carry over. Small, targeted change. +- **Same pipeline serves v10.x maintenance and stabilised v11.** When `release/v10.x` branches get cut for maintenance lines, the same workflow handles them — just set the flag. + +### Negative + +- **Maintainer has to remember the flag.** For genuinely-stabilised releases this is a small friction. Mitigation: documented in `docs/branching-and-release.md` step-by-step. +- **GitHub Packages is now load-bearing for v11.** Beta consumers (and us) rely on it as the primary release channel. If GH Packages has an outage, v11 release publishing is blocked until it recovers. The `--skip-duplicate` idempotency means retries are cheap, but the dependency is real. +- **Documentation churn.** Three docs needed updating (`docs/agents/release-and-versioning.md`, `docs/branching-and-release.md`, this ADR) shortly after ADR-0001 landed. + +### Neutral + +- **ADR-0001 stays accurate** as the description of the release-branch model itself. The Tier 1 / Tier 2 / Tier 3 taxonomy still applies. This ADR documents the *routing policy* on top of that model. +- **`project_release_channels` agent memory** captures the steady-state design. A separate memory entry (`project_v11_off_nuget`) captures the v11-specific carve-out. Future v12/v13 may revisit. + +## Alternatives considered + +### A. Branch-aware hard-coding in the workflow + +Conditional logic: `if ref == 'release/v11' skip nuget.org`. Hard-codes the policy per release branch. + +**Rejected because:** + +- Every new release branch (v10.x maintenance, v12 stabilisation) would need a YAML edit to register its routing. The flag-based approach is uniform across branches. +- "Stabilised v11" is a fuzzy boundary — at some point v11 starts going to nuget.org. Hard-coded ref checks force a YAML edit at that boundary; the flag lets the boundary be a per-release decision. + +### B. version.json field + +Per-branch field like `"publishToNugetOrg": true|false`. The workflow reads it. + +**Rejected because:** + +- Adds tooling (YAML reading version.json, decision logic). For a binary "should I publish or not" decision the input flag is sufficient. +- The decision is per-release, not per-branch (a v11 patch you trust enough for nuget.org vs one you don't). Per-branch config is the wrong granularity. + +### C. Two separate workflows + +`release.yml` for GitHub Packages only, `release-nugetorg.yml` for nuget.org. Maintainer picks which to invoke. + +**Rejected because:** + +- Two workflows duplicate test-and-pack work and double the YAML to maintain. +- The shared test-and-pack + artifact upload is what makes the multi-channel fan-out efficient. Splitting would lose that. + +## References + +- [ADR-0001: Release-branch model & multi-channel CD](0001-release-branch-model.md) — the parent decision. +- [RFC #267](https://github.com/ChrisonSimtian/Fallout/issues/267) — original design discussion. +- [docs/branching-and-release.md](../branching-and-release.md) — maintainer runbook (updated with the routine + stabilised release paths). +- [#47](https://github.com/ChrisonSimtian/Fallout/issues/47) — Nuke.* shims live on GitHub Packages permanently. + +## Memory artifacts (AI agent context) + +- `project_v11_off_nuget.md` — the v11-off-nuget direction as recorded in agent memory. +- `project_release_channels.md` — the broader channel taxonomy (steady-state model). diff --git a/docs/agents/release-and-versioning.md b/docs/agents/release-and-versioning.md index 533811f92..88ac383b0 100644 --- a/docs/agents/release-and-versioning.md +++ b/docs/agents/release-and-versioning.md @@ -73,23 +73,42 @@ If you only discover the breaking nature mid-review, apply all relevant steps be ## Release pipeline -`.github/workflows/release.yml` is **tag-triggered**: pushing a `v*` tag on a `release/v*` branch fires the pipeline. The workflow validates the tag is reachable from a `release/v*` branch, then fans out a Test+Pack job to three parallel publish jobs, one per GitHub Environment / channel tier: +`.github/workflows/release.yml` is **tag-triggered**: pushing a `v*` tag on a `release/v*` branch fires the pipeline. The workflow validates the tag is reachable from a `release/v*` branch, then fans out a Test+Pack job to three parallel publish jobs: -| Job | Environment | Channel | What ships | Gating | +| Job | Environment | Fires on tag push? | What ships | Gating | |---|---|---|---|---| -| `publish-nuget-org` | `nuget-org` | Tier 1 — production | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Approval-gated (maintainer reviewer) | -| `publish-github-packages` | `github-packages` | Tier 2 — bleeding edge | `Nuke.*.nupkg` transition shims to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None | -| `publish-github-releases` | `github-releases` | Bundled artifacts | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None | +| `publish-nuget-org` | `nuget-org` | **No — opt-in only** via `workflow_dispatch` flag | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Workflow flag + approval-gated env | +| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None | +| `publish-github-releases` | `github-releases` | Yes | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None | -`Nuke.*` shim packages are routed to GitHub Packages, not nuget.org — those package IDs are owned by the original NUKE maintainer on nuget.org (see [#47](https://github.com/ChrisonSimtian/Fallout/issues/47)). +### Why nuget.org is opt-in for v11 -Each `dotnet nuget push` uses `--skip-duplicate`, so re-runs of a partial publish (one channel failed transiently) are idempotent on the channels that already succeeded. +For v11, **GitHub Packages is the de-facto release channel.** nuget.org is reserved for v10.x maintenance lines and a future "stabilised" v11. To publish Fallout.* to nuget.org you must run `workflow_dispatch` with `publish-to-nugetorg=true` — a conscious "this release is ready for nuget.org" switch. Tag pushes alone publish to GitHub Packages + GitHub Releases only. -**Tag protection.** `v*` tags are protected via a repository ruleset (rules: creation, deletion, update). Bypass actors: repo admins only. Non-admins cannot create release tags — combined with the env approval gate on `nuget-org`, this gates "who can fire a production release" at two layers. +Two layers of protection on the nuget.org path: the input flag opt-in, plus the `nuget-org` environment's required-reviewer rule. -**Fallback trigger: `workflow_dispatch`.** Manual runs accept an existing-tag input — used to re-run the publish fan-out after a transient API failure. The workflow checks out the specified tag and re-runs the publish chain. `--skip-duplicate` keeps the re-run safe. +### Nuke.* shims -**Channel philosophy** (per [RFC #267](https://github.com/ChrisonSimtian/Fallout/issues/267)): nuget.org is slow/stable/production; GitHub Packages is faster/beta/bleeding-edge; GitHub Releases bundles artifacts on the tag. A planned Tier 3 (Docker-based local NuGet server for pre-merge testing) is tracked in [#279](https://github.com/ChrisonSimtian/Fallout/issues/279). +`Nuke.*` transition-shim package IDs are owned by the original NUKE maintainer on nuget.org (see [#47](https://github.com/ChrisonSimtian/Fallout/issues/47)) — they're permanently routed to GitHub Packages, never nuget.org, regardless of the input flag. + +### Re-runs + +Each `dotnet nuget push` uses `--skip-duplicate`, so re-runs of a partial publish (one channel failed transiently) are idempotent on packages that already succeeded. + +### Tag protection + +`v*` tags are protected via a repository ruleset (rules: creation, deletion, update). Bypass actors: repo admins only. Combined with the workflow-dispatch flag and env approval, the nuget.org path has *three* layers (tag-creation + flag opt-in + env approval). + +### `workflow_dispatch` inputs + +- `tag` (required) — existing tag to (re-)release. +- `publish-to-nugetorg` (boolean, default `false`) — opt into the nuget.org publish job for this run. + +Common use cases: re-running a transient-failed publish (`tag` only), or shipping a stabilised release to nuget.org (`tag` + `publish-to-nugetorg=true`). + +### Channel philosophy + +Per [RFC #267](https://github.com/ChrisonSimtian/Fallout/issues/267): nuget.org = production-grade & slow; GitHub Packages = faster cadence (currently the v11 release channel); GitHub Releases = bundled artifacts. A planned Tier 3 (Docker-based local NuGet server for pre-merge testing) shipped via [#279](https://github.com/ChrisonSimtian/Fallout/issues/279) — see `tests/integration/docker-compose.yml`. `NUGET_API_KEY` is scoped to the `nuget-org` GitHub Environment (per [#273](https://github.com/ChrisonSimtian/Fallout/issues/273)) — only resolves in the gated job. Prefix reservation tracked in [#33](https://github.com/ChrisonSimtian/Fallout/issues/33). diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md index 786480a2d..3b4c14b91 100644 --- a/docs/branching-and-release.md +++ b/docs/branching-and-release.md @@ -17,22 +17,22 @@ Maintainer reference for how Fallout branches, ships releases, hotfixes older ma ## Channel taxonomy -Releases fire to three independent channels, each with its own GitHub Environment: +Releases fire to multiple channels, each with its own GitHub Environment: -| Tier | Channel | Cadence | Gating | Versioning | -|---|---|---|---|---| -| **1** | `nuget-org` env → nuget.org | Slow, deliberate | **Approval-gated** (maintainer reviewer) | Clean semver (`11.0.0`, `11.0.1`, ...) | -| **2** | `github-packages` env → GitHub Packages | Faster — betas, previews | None | Prereleases or NB.GV builds | -| **3** | (planned) Docker local NuGet server | Per-PR / per-commit | None (ephemeral) | PR-derived | -| (bundled) | `github-releases` env → GitHub Release with nupkgs attached | Same tag as the package publish | None | Same as the tag | +| Tier | Channel | Cadence | Gating | Versioning | Current v11 use | +|---|---|---|---|---|---| +| **1** | `nuget-org` env → nuget.org | Slow, deliberate | **Workflow flag opt-in + approval-gated** | Clean semver (`11.0.0`, `11.0.1`, ...) | Reserved for v10.x maintenance + stabilised v11 (opt-in) | +| **2** | `github-packages` env → GitHub Packages | Faster — every tag | None | NB.GV-derived | **De-facto v11 release channel** (all tags publish here) | +| **3** | Docker local NuGet server | Per-PR / per-commit | None (local) | PR-derived | Available via `tests/integration/docker-compose.yml` | +| (bundled) | `github-releases` env → GitHub Release with nupkgs attached | Same tag as the package publish | None | Same as the tag | Every tag | -Tier 3 setup is tracked in [#279](https://github.com/ChrisonSimtian/Fallout/issues/279). See [`reference_release_channels` in agent memory](https://github.com/ChrisonSimtian/Fallout/issues/267#issuecomment-4570408325) for the design discussion. +For v11 specifically: GitHub Packages is the default destination on every tag push. nuget.org is opt-in via the `workflow_dispatch` `publish-to-nugetorg` flag — used when a release is stabilised enough to ship to the broader consumer audience. See [`project_release_channels` in agent memory](https://github.com/ChrisonSimtian/Fallout/issues/267#issuecomment-4570408325) and the v11-off-nuget direction memory. ## Cutting a release -The release pipeline fires on **tag push** to a `release/vN` branch. The tag must match `v*` and must point at a commit reachable from `release/v*` (the `validate-ref` job enforces this). +### Routine v11 release (GitHub Packages only) -### Step-by-step +The default path. Pushing a `v11.0.X` tag to `release/v11` publishes to GitHub Packages + GitHub Releases. nuget.org is **not** touched. ```bash # 1. Make sure your local release/vN is up to date @@ -50,29 +50,49 @@ gh release create v11.0.X \ --generate-notes ``` -`gh release create` publishes a tag + a GitHub Release in one call. That tag push triggers `.github/workflows/release.yml`, which: +That tag push triggers `.github/workflows/release.yml`: 1. **`validate-ref`** confirms the tag points at a commit reachable from `release/v*`. 2. **`test-and-pack`** runs `dotnet fallout Test Pack`, uploads `output/packages/*.nupkg` as an artifact. -3. **Three parallel publish jobs** consume the artifact and fan out per channel: - - `publish-nuget-org` → pushes `Fallout.*.nupkg` to nuget.org (**pauses for your approval**) - - `publish-github-packages` → pushes `Nuke.*.nupkg` transition shims to GitHub Packages - - `publish-github-releases` → attaches all `*.nupkg` to the GitHub Release page +3. Three parallel publish jobs consume the artifact: + - `publish-nuget-org` — **skipped** (not opt-in by default) + - `publish-github-packages` — pushes **all** `*.nupkg` (Fallout.* + Nuke.*) to GitHub Packages + - `publish-github-releases` — attaches all `*.nupkg` to the GitHub Release page + +### Stabilised release (nuget.org publish) + +When a v11 release is stabilised enough for nuget.org, or for cutting a v10.x maintenance patch, use `workflow_dispatch` with the opt-in flag: + +```bash +# Option A: via gh CLI +gh workflow run release.yml \ + -f tag=v11.0.X \ + -f publish-to-nugetorg=true -### Approving the `nuget-org` publish +# Option B: via Actions UI → release → "Run workflow" → set publish-to-nugetorg to true +``` + +The workflow: + +1. Skips `validate-ref` (workflow_dispatch doesn't auto-validate the ref; you took the action consciously). +2. Re-runs `test-and-pack` against the named tag. +3. **`publish-nuget-org` fires** — pauses for approval at the `nuget-org` env gate (notification + entry on the run page; click "Review deployments" → check `nuget-org` → "Approve and deploy"). Then pushes Fallout.* to nuget.org. +4. `publish-github-packages` re-runs idempotently (`--skip-duplicate` skips what's already there). +5. `publish-github-releases` re-runs idempotently (uses `--clobber` for asset replacement if the GH Release already exists). -When `publish-nuget-org` starts, it pauses for an environment-protection approval. You'll get a GitHub notification (and an entry on the run page). Click "Review deployments" → check `nuget-org` → "Approve and deploy". This is the production-grade gate — review what's about to publish before approving. +Two layers of safety on the nuget.org path: the flag opt-in + the env approval. You can also test the wiring without burning a release — set the flag, get the approval prompt, then cancel without approving. ### If a publish fails partway through -Each `dotnet nuget push` uses `--skip-duplicate`, so re-running a publish job is idempotent on packages that already made it through. If nuget.org transient-fails on one of fifteen packages: +Each `dotnet nuget push` uses `--skip-duplicate`. Re-running a publish job is idempotent on packages already pushed. For a transient failure mid-publish: ```bash -# Re-run the workflow against the same tag +# Routine re-run — leave publish-to-nugetorg false gh workflow run release.yml -f tag=v11.0.X -``` -The `workflow_dispatch` fallback re-checks out the tag and re-runs all three publish jobs. `--skip-duplicate` skips anything that already published. +# Stabilised re-run — include the flag if you want to retry the nuget.org push +gh workflow run release.yml -f tag=v11.0.X -f publish-to-nugetorg=true +``` ## Hotfixing an older major