Persist the --source override from aspire new aspire-empty into the scaffolded project
Summary
aspire new aspire-empty --language <typescript|...> --source <X> consumes --source only during the initial scaffold restore. The scaffolded project records the version and channel in aspire.config.json but does not capture the explicit --source feed. As a result, a later aspire restore or aspire add in the same project resolves Aspire packages from the configured channel feeds — not from <X> — and silently produces a different package set (or fails outright) compared with what the scaffold restore used.
PR #17166 ships a warning at scaffold time so the user is told the override is one-shot. This issue tracks closing the gap so --source is honored across the full lifecycle of the project, or rejected up front when that's not possible.
Repro
# Hive lives outside the standard channel feeds; user wants Aspire packages from there.
aspire new aspire-empty --language typescript --name MyApp --output ./MyApp \
--version 13.4.0-pr.NNNNN.gXXXXXXXX \
--source /path/to/pr-hive/packages
# Scaffold succeeds.
cd MyApp
aspire add redis # ← does NOT see /path/to/pr-hive/packages
aspire restore # ← same: only the channel feeds in aspire.config.json
For the C# empty path the gap is even larger: --source is dropped before scaffolding runs at all (the C# template ignores inputs.Source), so the initial restore also misses the override.
Why it matters
--source is the primary lever a user has to point Aspire at a non-default package set (PR hive, local pack, internal mirror). Honoring it only on the very first command surprises anyone who runs a follow-up CLI command in the same project — the natural next step.
Options
(a) Generate nuget.config in the scaffolded project
Write a nuget.config next to aspire.config.json that:
- Adds the explicit
--source as a package source.
- Adds the matching channel feeds as additional sources.
- Emits package source mapping (PSM) that pins
Aspire* to the explicit source.
Pros: subsequent aspire restore/aspire add resolve packages identically to the scaffold restore. The Aspire CLI already generates this exact shape today via TemporaryNuGetConfig — the same logic can be redirected at the project directory instead of a temp file.
Cons:
- Has to interact correctly with an existing
nuget.config in the parent directory tree (merge vs override).
- Credentials in a private feed (
--source https://user:pat@…) probably should not be committed; need a story for that (interactive prompt? sanitize?).
- Adds a file to source control that needs to be kept in sync if the channel changes later.
(b) Warn at scaffold time (shipped in #17166)
Tell the user the override is one-shot so they can take action manually (add the feed to their own nuget.config, or move the packages to a channel feed). Lowest-friction, no design risk, but doesn't solve the underlying gap.
(c) Reject at scaffold time when the override is not also a registered channel
Fail aspire new --source <X> unless <X> matches one of the explicit channels. Highest correctness, lowest convenience — the common dogfood case (point me at this PR's hive) becomes a two-step setup.
(d) Also honor --source on the C# empty path
Today the C# empty template (WriteCSharpEmptyAppHostAsync) ignores inputs.Source entirely. Whichever direction (a)/(c) is chosen above, the C# path should match.
Suggested direction
Lean toward (a) + (d): emit nuget.config with PSM and apply it to both C# and non-C# empty templates. Pair with the existing TemplateNuGetConfigService path so the same prompt + merge behavior applies regardless of language. Handle credential-bearing source URIs by stripping userinfo from the persisted form and warning the user that auth has to be configured separately.
Out of scope
Refs
Persist the
--sourceoverride fromaspire new aspire-emptyinto the scaffolded projectSummary
aspire new aspire-empty --language <typescript|...> --source <X>consumes--sourceonly during the initial scaffold restore. The scaffolded project records the version and channel inaspire.config.jsonbut does not capture the explicit--sourcefeed. As a result, a lateraspire restoreoraspire addin the same project resolves Aspire packages from the configured channel feeds — not from<X>— and silently produces a different package set (or fails outright) compared with what the scaffold restore used.PR #17166 ships a warning at scaffold time so the user is told the override is one-shot. This issue tracks closing the gap so
--sourceis honored across the full lifecycle of the project, or rejected up front when that's not possible.Repro
For the C# empty path the gap is even larger:
--sourceis dropped before scaffolding runs at all (the C# template ignoresinputs.Source), so the initial restore also misses the override.Why it matters
--sourceis the primary lever a user has to point Aspire at a non-default package set (PR hive, local pack, internal mirror). Honoring it only on the very first command surprises anyone who runs a follow-up CLI command in the same project — the natural next step.Options
(a) Generate
nuget.configin the scaffolded projectWrite a
nuget.confignext toaspire.config.jsonthat:--sourceas a package source.Aspire*to the explicit source.Pros: subsequent
aspire restore/aspire addresolve packages identically to the scaffold restore. The Aspire CLI already generates this exact shape today viaTemporaryNuGetConfig— the same logic can be redirected at the project directory instead of a temp file.Cons:
nuget.configin the parent directory tree (merge vs override).--source https://user:pat@…) probably should not be committed; need a story for that (interactive prompt? sanitize?).(b) Warn at scaffold time (shipped in #17166)
Tell the user the override is one-shot so they can take action manually (add the feed to their own
nuget.config, or move the packages to a channel feed). Lowest-friction, no design risk, but doesn't solve the underlying gap.(c) Reject at scaffold time when the override is not also a registered channel
Fail
aspire new --source <X>unless<X>matches one of the explicit channels. Highest correctness, lowest convenience — the common dogfood case (point me at this PR's hive) becomes a two-step setup.(d) Also honor
--sourceon the C# empty pathToday the C# empty template (
WriteCSharpEmptyAppHostAsync) ignoresinputs.Sourceentirely. Whichever direction (a)/(c) is chosen above, the C# path should match.Suggested direction
Lean toward (a) + (d): emit
nuget.configwith PSM and apply it to both C# and non-C# empty templates. Pair with the existingTemplateNuGetConfigServicepath so the same prompt + merge behavior applies regardless of language. Handle credential-bearing source URIs by stripping userinfo from the persisted form and warning the user that auth has to be configured separately.Out of scope
--sourceforaspire initand the .NET starter templates — separate path, separate UX.--sourceinteracts with--channelat restore time (already exclusive forAspire*packages after fix(cli): keep guest AppHost restore on the selected Aspire package source #17166).Refs
aspire new aspire-empty --language typescriptfails with TypeLoadException when using PR hive packages #17159 (the original bug that surfaced this)