Skip to content

Commit a5df534

Browse files
authored
Proposal to Surface Sponsorship Information in the CLI (#14992)
1 parent 2f9b08e commit a5df534

1 file changed

Lines changed: 396 additions & 0 deletions

File tree

‎accepted/2026/sponsorship-cli.md‎

Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
# **Proposal to Surface Sponsorship Information in the CLI**
2+
3+
- Author: [@kalebfik](https://github.com/kalebfik)
4+
- GitHub Issue: [10703](https://github.com/NuGet/NuGetGallery/issues/10703)
5+
- [Accompanying Technical Implementation Spec](https://github.com/NuGet/Home/blob/dev-kalebfika-sponsorTechSpec/accepted/2026/implementing-sponsorship-in-cli.md)
6+
7+
## Summary
8+
9+
This spec proposes surfacing sponsorship links in the command line using the command `dotnet package list --sponsor`, so that users are able to see which packages they use are looking for funding.
10+
This feature is opt-in and reports sponsorship information throughout a project.
11+
12+
## Motivation
13+
14+
Package maintainers can already add sponsorship links (GitHub Sponsors, Patreon, Open Collective, etc.) to their packages on nuget.org. [See the package sponsorship documentation for more information](https://learn.microsoft.com/en-us/nuget/nuget-org/package-sponsorship-on-nuget-org).
15+
Developers only see this today if they visit the package's nuget.org page.
16+
As a result, consumers who primarily use the command line to navigate their packages may not know that the dependencies they rely on are seeking support.
17+
The maintainers of those packages receive limited visibility for sponsorship links they may have provided on the website.
18+
19+
The CLI is an appropriate place to add sponsorship information because there is already a way to review current dependencies using `dotnet package list`.
20+
This is the most relevant workflow to implement sponsorship information.
21+
22+
## Goals
23+
24+
- Make sponsorship information discoverable through the CLI using `dotnet package list --sponsor`
25+
- Help consumers and organizations identify packages seeking financial support
26+
- Increase visibility for sponsorship links provided by package maintainers
27+
- Keep the experience explicit and separate from restore
28+
29+
## Non-Goals
30+
31+
- Process sponsorship payments
32+
- Management of sponsorship links from the CLI
33+
- Display sponsorship features during restore
34+
- Validation of sponsorship links
35+
- Ranking or prioritizing sponsorship links from the CLI
36+
37+
**Why not restore?**
38+
39+
Restore is a process that is used frequently during builds and other background IDE operations.
40+
Sponsorship information is not required to resolve builds or install packages, so retrieving that information during the restore process could add network costs to a performance-sensitive operation without providing adequate justification for the extra cost.
41+
Keeping it opt-in through the `--sponsor` flag ensures that it is retrieved only when the user is explicitly asking for it.
42+
43+
**Who this affects**
44+
45+
- **Package consumers** get a way to discover, from the command line, which of their dependencies are seeking sponsorship — without having to visit nuget.org for every package individually.
46+
- **Package authors** who've already added sponsorship links to their packages get more visibility for those links, since consumers can now see them as part of a workflow (`dotnet package list --sponsor`), not just on the package's web page.
47+
- **Repository/org admins** have an easier way to see sponsorship status alongside the other package information they already manage day-to-day (versions, licenses, vulnerabilities).
48+
49+
## Explanation
50+
51+
### Functional Explanation
52+
53+
**Proposed Experience**
54+
55+
When a user runs `dotnet package list --sponsor`, output is grouped by project and the source used, and includes sponsorship links for each matching package:
56+
57+
`dotnet package list --sponsor` output:
58+
59+
```text
60+
// sample response
61+
Project 'Contoso.App' has the following sponsorable packages
62+
Top-level Package Sponsor
63+
> Contoso.Tools Source: https://api.example.org/v3/index.json
64+
https://github.com/sponsors/contoso
65+
Transitive Package
66+
> Contoso.Utility Source: https://api.example.org/v3/index.json
67+
https://github.com/sponsors/contoso
68+
Source: https://www.myget.org/F/contoso/api/v3/index.json
69+
https://buymeacoffee.com/contoso
70+
```
71+
72+
Sponsorship information is applied to the package ID rather than the version of a package.
73+
Therefore, sponsorship information remains the same for a package regardless of which package version is published, as long as those links have not changed.
74+
When a package has multiple sponsorship links, the CLI will display the links in the order returned by nuget.org.
75+
76+
Both top-level and transitive packages will be included by default when using `--sponsor`.
77+
This behavior will be documented in the command's help description.
78+
79+
The command also supports `dotnet package list --sponsor --format json` and will produce output such as:
80+
81+
```json
82+
{
83+
"version": 1,
84+
"parameters": "--sponsor --format json",
85+
"sources": [
86+
"https://api.example.org/v3/index.json",
87+
"https://www.myget.org/F/contoso/api/v3/index.json"
88+
],
89+
"packages": [
90+
{
91+
"id": "Contoso.Tools",
92+
"projects": [
93+
{
94+
"path": "/path/to/Contoso.App.csproj",
95+
"relationship": "topLevel"
96+
}
97+
],
98+
"sponsorships": [
99+
{
100+
"sources": [
101+
"https://api.example.org/v3/index.json"
102+
],
103+
"urls": [
104+
"https://github.com/sponsors/contoso"
105+
]
106+
}
107+
]
108+
},
109+
{
110+
"id": "Contoso.Utility",
111+
"projects": [
112+
{
113+
"path": "/path/to/Contoso.App.csproj",
114+
"relationship": "transitive"
115+
}
116+
],
117+
"sponsorships": [
118+
{
119+
"sources": [
120+
"https://api.example.org/v3/index.json"
121+
],
122+
"urls": [
123+
"https://github.com/sponsors/contoso"
124+
]
125+
},
126+
{
127+
"sources": [
128+
"https://www.myget.org/F/contoso/api/v3/index.json"
129+
],
130+
"urls": [
131+
"https://buymeacoffee.com/contoso"
132+
]
133+
}
134+
]
135+
}
136+
]
137+
}
138+
```
139+
140+
Each package source is listed once, with the projects where it is used and its top-level or transitive relationships recorded.
141+
If multiple sources return the same ordered URL list, the JSON represents the list once and includes all sources in the same `sponsorships` entry.
142+
If multiple sources return different URL lists, they are represented in separate `sponsorships` entries.
143+
144+
**No Sponsorship Details Returned**
145+
146+
The command selects enabled package sources from NuGet configuration.
147+
When a source is specified using `--source <SOURCE>`, only that source is queried for sponsorship information.
148+
A successful Registration response with a missing or empty `sponsorshipUrls` field is treated as a successful empty result.
149+
150+
Console output includes only sources that return one or more sponsorship URLs.
151+
`https://api.nuget.org/v3/index.json` will be recommended when it is not part of the user's configured sources.
152+
If none of the selected sources return sponsorship details for a project, the CLI displays:
153+
154+
```text
155+
// sample response
156+
No sponsorship details were returned using the following package sources:
157+
https://api.example.org/v3/index.json
158+
159+
Consider specifying an additional package source that provides sponsorship metadata, for example: `--source https://api.nuget.org/v3/index.json`.
160+
```
161+
162+
A source that does not support sponsorships will have a separate message:
163+
164+
```text
165+
These sources do not provide sponsorship support:
166+
C:/Program Files (x86)/Microsoft SDKs/NuGetPackages/
167+
```
168+
169+
The JSON output includes every selected source in the top-level `sources` list, which includes supported and unsupported sources, along with local sources.
170+
Only packages that have at least one sponsorship URL will appear in `packages`.
171+
For an empty report, `packages` is empty, and `problems` contains one warning per empty or unsupported source.
172+
173+
```json
174+
{
175+
"version": 1,
176+
"parameters": "--sponsor --format json",
177+
"sources": [
178+
"https://api.example.org/v3/index.json",
179+
"C:/Program Files (x86)/Microsoft SDKs/NuGetPackages/"
180+
],
181+
"problems": [
182+
{
183+
"level": "warning",
184+
"text": "There are no sponsorship details found at https://api.example.org/v3/index.json"
185+
},
186+
{
187+
"level": "warning",
188+
"text": "This source does not provide sponsorship support: C:/Program Files (x86)/Microsoft SDKs/NuGetPackages/"
189+
}
190+
],
191+
"packages": []
192+
}
193+
```
194+
195+
**Package Sources, Package Source Mapping (PSM), and `--source`**
196+
197+
A package source advertises sponsorship support through a new version of the Registration resource.
198+
The exact Registration version will be finalized with the server implementation.
199+
200+
```json
201+
{
202+
"@id": "https://api.nuget.org/v3/registration5-semver1/",
203+
"@type": "RegistrationsBaseUrl/{NEW_VERSION}",
204+
"comment": "Base URL of Azure storage where NuGet package registration info is stored. This base URL does not include SemVer 2.0.0 packages."
205+
}
206+
```
207+
208+
Registration requests may disclose package IDs to package sources; therefore, sponsorship reporting will honor Package Source Mapping (PSM).
209+
When PSM is disabled, each package is queried against all configured sources.
210+
When PSM is enabled, the client queries only sources that are mapped to that package.
211+
212+
The command displays an informational message when using the command with PSM enabled:
213+
214+
```text
215+
Package Source Mapping is enabled. Sponsorship details will be requested from sources mapped to packages.
216+
```
217+
218+
Users will encounter an error if PSM is combined with `--source`.
219+
The command stops before making any sponsorship requests, and the CLI will produce an error message:
220+
221+
```text
222+
Package Source Mapping is enabled and cannot be combined with `--source` for sponsorship reporting.
223+
```
224+
225+
PSM is intended to ensure each package is accessed only through package sources that are explicitly mapped in a user's configuration.
226+
Using `--source` replaces the configured source selection and could select a source that is not represented by those mappings.
227+
Instead of bypassing or ignoring PSM, the command fails before making any requests.
228+
A user must add the desired source and package mappings to NuGet configuration or use a configuration without PSM.
229+
230+
A mapped source that is successfully queried but returns an empty `sponsorshipUrls` property produces the same empty-source behavior present in the JSON above.
231+
This includes sources that do not propagate sponsorship metadata from an upstream source.
232+
233+
| Scenario | Who this represents | Behavior |
234+
|---|---|---|
235+
| **NuGet.org only; no PSM** | A developer using the default public NuGet ecosystem. | Query every top-level and transitive package ID against NuGet.org. |
236+
| **NuGet.org and other sources; no PSM** | A developer or organization using public and private feeds. | Query every package ID against each selected sponsorship-capable source; retain unsupported selected sources as warnings in JSON. Group results by source. |
237+
| **Only private or third-party sources; no PSM** | A developer or organization not using NuGet.org directly. | Query every package ID against each selected sponsorship-capable source. Report other selected sources as unsupported. |
238+
| **PSM enabled** | An organization restricting package IDs to configured sources. | Query each package ID only against its mapped, sponsorship-capable configured sources. |
239+
| **PSM enabled with `--source`** | A user attempting to override configured source selection while mappings are active. | Fail before any sponsorship request and explain that PSM cannot be combined with `--source`. |
240+
241+
### Technical Explanation
242+
243+
The CLI will retrieve sponsorship information through the Registration API, similar to how other package metadata is passed to the CLI.
244+
The difference is that sponsorship information will be scoped by package ID only, whereas other metadata is scoped by package ID and version.
245+
246+
A successful nuget.org response with one or more URLs will appear in the report, while packages with no sponsorship URLs will be absent.
247+
248+
The client will display the URLs exactly as each source returns them.
249+
There is no validation, ranking, or prioritization done on the client side.
250+
251+
A package source will indicate sponsorship support through a `metadata.sponsorshipUrls` field on the Registration index root:
252+
253+
**Example registration root entry with `metadata` and `sponsorshipUrls` added:**
254+
255+
```jsonc
256+
{
257+
"@id": "https://api.nuget.org/v3/registration5-gz-semver2/contoso.webapi.client/index.json",
258+
"@type": [
259+
"catalog:CatalogRoot",
260+
"PackageRegistration",
261+
"catalog:Permalink"
262+
],
263+
"commitId": "afa91af1-9505-41b8-ad75-eab8e613db14",
264+
"commitTimeStamp": "2026-04-10T00:15:25.1492389+00:00",
265+
"count": 2,
266+
// ** Start of proposal ** //
267+
"metadata": {
268+
"sponsorshipUrls": [
269+
"https://github.com/sponsors/contoso"
270+
]
271+
}
272+
// ** End of proposal ** //
273+
}
274+
```
275+
276+
- **Scope:** solution-wide across all projects.
277+
- **Transitive packages:** Included by default; when using `dotnet package list --help`, the description will state that transitive packages are included by default.
278+
- **Multiple sponsorship URLs:** The CLI displays sponsorship URLs in the order received from the package source.
279+
- **nuget.org URL limit:** nuget.org currently enforces a maximum of 10 URLs per package.
280+
- **Empty state:** A missing, null, or empty `sponsorshipUrls` value is treated as a successful empty result.
281+
282+
## Drawbacks
283+
284+
- At this time, nuget.org is the only source that supports sponsorship details in the Registration index root.
285+
- Other sources will produce a message saying that source does not support sponsorship reporting.
286+
- Sponsorship details from an Azure Artifacts upstream source are not shown unless that source exposes those details through its own Registration response.
287+
288+
## Rationale and Alternatives
289+
290+
Why `dotnet package list --sponsor`? Why not `dotnet package sponsor` or `dotnet package fund`?
291+
292+
```bash
293+
dotnet package sponsor
294+
```
295+
296+
This approach treats sponsorships similarly to npm's `npm fund` command (see the appendix).
297+
298+
A dedicated command could provide a clearer and more focused intent while also supporting future interactive experiences.
299+
That said, the intent for the proposed experience is to report package metadata, not to provide actions like selecting a provider or processing a sponsorship.
300+
If sponsorship grows into a bigger feature, a dedicated command could be revisited.
301+
302+
## Prior Art/Related
303+
304+
- [**Package sponsorships on nuget.org**](https://learn.microsoft.com/en-us/nuget/nuget-org/package-sponsorship-on-nuget-org): Package sponsorships can already be configured on nuget.org.
305+
- [**Companion server spec**](https://devdiv.visualstudio.com/DevDiv/_git/NuGet.Services/pullrequest/763096?_a=files&iteration=2&base=1): Proposes implementation for supporting package ID-level metadata in the Registration API (internal link).
306+
- **[`npm fund`](https://docs.npmjs.com/cli/v10/commands/npm-fund/)**: `npm fund` provides precedent for this pattern. The `funding` field lives in package metadata, and npm's guidance suggests keeping funding links at the package or author level.
307+
npm notes that funding information can be noisy in the CLI and that stale information could be problematic.
308+
- **`--deprecated`/`--vulnerable`**: precedent for opt-in report-style information that consumers already use and understand, informing this proposal's output/UX conventions.
309+
- **PM UI's existing project/license/report-abuse links**: precedent for Phase 2 — PM UI already shows package-supplied links to consumers using an established, low-risk pattern.
310+
- **[`nuget/home#14739`](https://github.com/nuget/home/issues/14739)**: Open issue for a PM UI sponsorship button.
311+
312+
## Unresolved Questions
313+
314+
None at this time.
315+
316+
## Future Possibilities
317+
318+
- **Multiple package sources:** Other sources adopt the new Registration resource version and can report sponsorship details.
319+
- **Dedicated sponsorship command:** Introduce a `dotnet package sponsor` or `dotnet package fund` command if sponsorships develop into a larger workflow.
320+
- **Interactive CLI experience:** Allow users to navigate a package's sponsorship links using their arrow keys and open one in their native browser.
321+
- **Filter top-level and transitive packages.**
322+
- **VS Package Manager UI hyperlinks** for sponsorship links, directly addressing [nuget/home#14739](https://github.com/nuget/home/issues/14739).
323+
- **AI agent integration:** Make sponsorship information available to package-management agents. See [Issue 14738](https://github.com/NuGet/Home/issues/14738).
324+
325+
## Appendix
326+
327+
### npm Fund Comparison
328+
329+
npm provides a dedicated `npm fund` command that helps developers discover funding opportunities for packages they depend on.
330+
Package authors specify funding information directly in their package metadata using the `funding` field.
331+
332+
```json
333+
{
334+
"funding": {
335+
"type": "individual",
336+
"url": "http://example/donate"
337+
},
338+
339+
"funding": {
340+
"type": "patreon",
341+
"url": "https://domain/my-account"
342+
}
343+
}
344+
```
345+
346+
When a package containing funding metadata is installed, npm displays a summary message:
347+
348+
```text
349+
Added 342 packages in 4s
350+
351+
3 packages are looking for funding
352+
run `npm fund` for details
353+
```
354+
355+
After running `npm fund`, users receive this output:
356+
357+
```text
358+
my-project
359+
├── https://domain/sponsors/user
360+
│ └── express@4.21.2
361+
├── https://domain/user
362+
│ └── @babel/core@7.28.0
363+
└── https://domain/sponsors/user
364+
└── p-limit@7.1.1
365+
```
366+
367+
**Characteristics of npm's approach**
368+
369+
- Funding information is stored within package metadata.
370+
- Funding information is distributed through the package ecosystem.
371+
- Funding discovery has a dedicated command surface (`npm fund`).
372+
373+
**Comparison with Proposed Approach**
374+
375+
npm: Funding information is directly embedded in package metadata and distributed throughout the npm ecosystem.
376+
377+
```bash
378+
npm fund
379+
```
380+
381+
Dedicated funding workflow.
382+
383+
NuGet: Sponsorship information is retrieved from the selected package source and surfaced through client tooling.
384+
385+
```bash
386+
dotnet package list --sponsor
387+
```
388+
389+
Report-style experience aligned with existing commands.
390+
391+
**Why not mirror npm exactly?**
392+
393+
1. Sponsorship information needs to be queried from the package source rather than embedded within the package.
394+
2. Sponsorship discovery follows existing NuGet resolution behavior.
395+
3. Restore output remains unchanged to avoid additional noise.
396+
4. Users explicitly opt into sponsorship discovery instead of receiving sponsorship messaging during restore operations.

0 commit comments

Comments
 (0)