Package publishing for Ref12: GitHub Releases used as an anonymous NuGet v3 feed (registration index only).
Source URL: https://github.com/Ref12/packages/releases/download/feed/index.json
| Release | Holds |
|---|---|
feed |
the service index (index.json), with RegistrationsBaseUrl/3.6.0 = https://github.com/Ref12/packages/releases/download/ |
<id> (lowercase id) |
index.json: the generated registration index of that package, inline items |
<id>-<version> |
<id>.<version>.nupkg, <id>.<version>.nuspec, and an unlisted marker when unlisted |
No slash tags (git cannot have tag x and x/1.0). Ids and versions are lowercase in names; versions are NuGet-normalized (1.0.0.0 -> 1.0.0, build metadata dropped).
nuget.config (next to your solution). packageSourceMapping makes only the allowed ids come from here, so nobody can shadow them from another feed, and nothing else is asked of this feed:
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="ref12" value="https://github.com/Ref12/packages/releases/download/feed/index.json" protocolVersion="3" />
</packageSources>
<packageSourceMapping>
<packageSource key="ref12">
<package pattern="Ref12.FeedProbe" />
<package pattern="Ref12.WasmNative" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>Keep the patterns in line with packages.json. No credentials are needed; downloads follow GitHub's 302 to signed asset URLs.
A package may be published only if its id and source repo are in packages.json:
{ "packages": [
{ "id": "Ref12.FeedProbe", "repos": ["Ref12/packages"], "nugetOrg": false },
{ "id": "Ref12.WasmNative", "repos": ["Ref12/packages"], "nugetOrg": false }
] }An exact id entry wins over prefixes; prefixes are plain case-insensitive "starts with" (Ref12.Wasm also matches Ref12.WasmSdk). nugetOrg must be true before nuget_org: true is accepted for that id. Change the file by pull request.
gh workflow run publish.yml -R Ref12/packages \
-f repo=Ref12/some-lib -f tag=v1.2.3 -f asset=Some.Lib.1.2.3.nupkggh workflow run publish.yml -R Ref12/packages \
-f repo=Ref12/packages -f ref=main -f project=probe/Ref12.FeedProbeAdd -f nuget_org=true to also push to nuget.org (see below).
Needs a secret PACKAGES_DISPATCH_TOKEN in that repo: a fine-grained token limited to Ref12/packages with Contents: read and write (required by the dispatch API). repository_dispatch only runs the workflow on the default branch.
on:
release:
types: [published]
jobs:
publish-package:
runs-on: ubuntu-latest
steps:
# ...your steps that build the .nupkg and attach it to the release as Some.Lib.${{ github.event.release.tag_name }}.nupkg...
- name: Ask Ref12/packages to publish it
env:
GH_TOKEN: ${{ secrets.PACKAGES_DISPATCH_TOKEN }}
run: |
gh api repos/Ref12/packages/dispatches \
-f event_type=publish \
-f 'client_payload[repo]=${{ github.repository }}' \
-f 'client_payload[tag]=${{ github.event.release.tag_name }}' \
-f 'client_payload[asset]=Some.Lib.*.nupkg'client_payload keys are the workflow inputs: repo, tag + asset or ref + project, optional nuget_org. Private source repos need a SOURCE_REPO_TOKEN secret here (read access to contents); public repos need nothing.
- The package is read, not trusted: id and version come from the nuspec inside the nupkg, never from the inputs or the file name.
- Release source: the tag must equal the nuspec version (
v1.2.3or1.2.3). - The source repo must be listed in
packages.jsonbefore anything is fetched or built, and the id must be allowed for that repo after the nuspec is read. - Fetch/build runs in a read-only job; only the later job holding
contents: writetouches releases. - Versions are immutable: if release
<id>-<version>exists the publish fails. Publish a new version, or unlist. - One index rebuild at a time per package id (
concurrency: index-<id>). The version release is created before the index job queues, and each index run rebuilds from all releases, so a queued run superseded by a newer one loses nothing. If an index job fails, runreindex. - nuget.org only through Trusted Publishing (OIDC, short-lived key); there is no API key secret anywhere.
- Unlist (hide from search, keep downloadable):
gh workflow run unlist.yml -R Ref12/packages -f id=Ref12.FeedProbe -f version=0.0.2. Add-f relist=trueto undo. This adds/removes theunlistedasset and regenerates the index. NuGet still restores an exact unlisted version. - Reindex (repair):
gh workflow run reindex.yml -R Ref12/packages -f id=Ref12.FeedProbe; withoutidit rebuilds every package index and the service index. Releases made before nuspec assets existed are read from their nupkg.
- On nuget.org: sign in as the owner account, then Trusted Publishing (https://www.nuget.org/account/trustedpublishing) -> add a policy:
- Repository Owner:
Ref12 - Repository:
packages - Workflow File:
publish.yml - Environment:
nuget-org
- Repository Owner:
- In GitHub (Ref12/packages) -> Settings -> Environments: create
nuget-org(optionally with required reviewers). - Set the variable
NUGET_USERto your nuget.org profile name (not email): Settings -> Secrets and variables -> Actions -> Variables (repository, or on thenuget-orgenvironment). - Set
"nugetOrg": trueon the package's entry inpackages.json. - Publish with
-f nuget_org=true.
Until NUGET_USER is set the nuget.org job prints a notice and skips. Docs: https://learn.microsoft.com/nuget/nuget-org/trusted-publishing
tools/Feed: .NET 8 console app (not a single-file app, so tests can reference it).dotnet run --project tools/Feed -- verify|allowed-repo|index ...tools/reindex.sh: collects release metadata withgh, runs the tool, uploads indexes.dotnet test tests/Feed.Tests: version ordering, index generation on fixtures (versions, prereleases, dependency groups, unlisted, paging), allowlist and verify checks, and a realdotnet restorefrom a local feed built by the generator and served over HTTP with a GitHub-style 302. Runs on ubuntu and windows (test.yml).restore-test.yml: restores the probe from the live feed on ubuntu and windows.wasm-native/: Ref12.WasmNative (workload-free relink/AOT SDK for browser-wasm), see its README; CI inwasm-native.yml.feed-b/,probe/: the original experiment and the probe package.