Add git-based template system spec and command stubs#14763
Add git-based template system spec and command stubs#14763mitchdenny wants to merge 28 commits intomainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14763Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14763" |
There was a problem hiding this comment.
Pull request overview
Introduces the initial (feature-flagged) surface area for a git-based template system in the Aspire CLI, along with a comprehensive architecture specification to guide subsequent implementation phases.
Changes:
- Added
docs/specs/git-templates.mddescribing the proposed git-based template architecture, security model, caching, and CLI integration. - Added stub
aspire templatecommand group and subcommands gated behindfeatures.gitTemplatesEnabled. - Wired feature flag metadata and DI registrations for the new command stubs.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Aspire.Cli/Program.cs | Registers the new template command stubs in DI. |
| src/Aspire.Cli/KnownFeatures.cs | Adds gitTemplatesEnabled feature flag and metadata. |
| src/Aspire.Cli/Commands/RootCommand.cs | Adds the aspire template command group to the root command when the feature flag is enabled. |
| src/Aspire.Cli/Commands/Template/BaseTemplateSubCommand.cs | Adds a shared base for template subcommands (update notifications disabled). |
| src/Aspire.Cli/Commands/Template/TemplateCommand.cs | Adds the aspire template parent command (stub behavior shows help). |
| src/Aspire.Cli/Commands/Template/TemplateListCommand.cs | Adds stub aspire template list. |
| src/Aspire.Cli/Commands/Template/TemplateSearchCommand.cs | Adds stub aspire template search <keyword>. |
| src/Aspire.Cli/Commands/Template/TemplateRefreshCommand.cs | Adds stub aspire template refresh. |
| src/Aspire.Cli/Commands/Template/TemplateNewManifestCommand.cs | Adds stub aspire template new [path]. |
| src/Aspire.Cli/Commands/Template/TemplateNewIndexCommand.cs | Adds stub aspire template new-index [path]. |
| docs/specs/git-templates.md | Adds the full git-templates specification document. |
| TelemetryCommand telemetryCommand, | ||
| DocsCommand docsCommand, | ||
| SdkCommand sdkCommand, | ||
| TemplateCommand templateCommand, |
There was a problem hiding this comment.
RootCommand is in Aspire.Cli.Commands, and there is already an existing Aspire.Cli.Commands.TemplateCommand type (used for per-template commands). The newly added git-template command group is Aspire.Cli.Commands.Template.TemplateCommand, but this constructor parameter will bind to the existing Aspire.Cli.Commands.TemplateCommand instead. That type can't be created by DI (it requires an ITemplate and callback), so CLI startup will fail. Fix by renaming the new command type (e.g., GitTemplateCommand) or by explicitly qualifying/importing Aspire.Cli.Commands.Template and ensuring the parameter type is the new command-group command.
| TemplateCommand templateCommand, | |
| Template.TemplateCommand templateCommand, |
| Subcommands.Add(sdkCommand); | ||
| } | ||
|
|
||
| if (featureFlags.IsFeatureEnabled(KnownFeatures.GitTemplatesEnabled, false)) |
There was a problem hiding this comment.
Adding the template command group introduces new DI requirements for RootCommand. The CLI host (Program.cs) registers these new command types, but the unit-test service registration helper (tests/Aspire.Cli.Tests/Utils/CliTestHelper) currently mirrors command registrations and will fail to resolve RootCommand unless it is updated to also register the new template command + subcommands. Please update the test DI registrations (or make this dependency optional/lazy) to keep CLI unit tests passing.
| if (featureFlags.IsFeatureEnabled(KnownFeatures.GitTemplatesEnabled, false)) | |
| if (featureFlags.IsFeatureEnabled(KnownFeatures.GitTemplatesEnabled, false) && | |
| templateCommand is not null) |
🎬 CLI E2E Test RecordingsThe following terminal recordings are available for commit
📹 Recordings uploaded automatically from CI run #22574198507 |
1b5399e to
86bbe8b
Compare
Comprehensive spec for a git-native, federated template system for the Aspire CLI. Templates are working Aspire apps personalized via string substitution, hosted in git repositories with federated discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stub out the 'aspire template' command group behind the gitTemplatesEnabled feature flag. Commands added: - aspire template list - aspire template search <keyword> - aspire template refresh - aspire template new [path] - aspire template new-index [path] All commands output stub messages. The command tree is hidden by default and can be enabled with: aspire config set features.gitTemplatesEnabled true Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Qualify Template.TemplateCommand in RootCommand constructor to disambiguate from the existing Aspire.Cli.Commands.TemplateCommand (used for dotnet new-style template commands). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Define C# models for aspire-template.json and aspire-template-index.json: - GitTemplateManifest, GitTemplateIndex, GitTemplateVariable, GitTemplateSubstitutions, and related types - GitTemplateJsonContext for source-generated JSON serialization Implement interactive scaffolding in template commands: - 'aspire template new [path]' prompts for name, display name, description, and language, then writes aspire-template.json with starter variables and substitution rules - 'aspire template new-index [path]' prompts for publisher info and writes aspire-template-index.json with a placeholder entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The publisher URL is the GitHub URL the index was fetched from, so there's no need to prompt for it. The command now writes a minimal index file with a placeholder template entry and no interactive prompts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Strip displayName, description, and minAspireVersion from the schema models and scaffolding commands. Keep the models minimal — these can be added back incrementally when needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement the template index resolution pipeline: - GitTemplateSource: identifies an index source (repo URL + ref) - GitTemplateCache: local file cache with TTL in ~/.aspire/cache/git-templates/ - IGitTemplateIndexService / GitTemplateIndexService: fetches indexes from GitHub via raw.githubusercontent.com, walks include graph with cycle detection and depth limit (5), caches results Wire up commands to the index service: - 'aspire template list' fetches and displays all templates in a table - 'aspire template search <keyword>' filters by name/tags - 'aspire template refresh' clears cache and re-fetches Sources are configured via: - templates.indexes.default.repo (defaults to dotnet/aspire) - templates.indexes.default.ref (defaults to release/latest) - templates.indexes.<name>.repo for additional sources Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement the core template application pipeline:
- TemplateExpressionEvaluator: evaluates {{var}} and {{var | filter}}
expressions with filters: lowercase, uppercase, kebabcase,
snakecase, camelcase, pascalcase
- IGitTemplateEngine / GitTemplateEngine: applies a template by
copying files from a template directory to an output directory
with filename and content substitutions, conditional file
exclusion, binary file detection (extension allowlist + null-byte
sniffing), and post-creation message display
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add GitTemplate and GitTemplateFactory to make git-hosted templates appear alongside dotnet new templates in 'aspire new': - GitTemplate: ITemplate that clones a template repo (sparse checkout), prompts for variables, and applies via GitTemplateEngine - GitTemplateFactory: ITemplateFactory that yields GitTemplate instances from the resolved index - Registered via TryAddEnumerable alongside DotNetTemplateFactory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Local paths (absolute, relative, or Windows drive paths) are now supported as index sources. This enables template development workflows: aspire config set templates.indexes.dev.repo /path/to/my-templates Local indexes are never cached — they're always read fresh from disk so changes are reflected immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetAllConfigurationAsync returns dot-separated keys (e.g. templates.indexes.dev.repo) but the code was filtering for colon-separated keys (templates:indexes:dev:repo). Fixed to use dot notation consistently. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Telemetry.StartDiagnosticActivity() to list, search, refresh commands - Add try/catch with user-friendly error messages for network failures - Add sample aspire-template-index.json at repo root for default index - Track template counts and search keywords in telemetry tags Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For local template sources, FetchTemplateAsync was incorrectly using git clone with sparse checkout, which doesn't work for local directory-based templates. Now directly copies files from the resolved local path, skipping .git directories. Also adds IsLocalPath and CopyDirectoryRecursive helpers to GitTemplate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements IGitHubCliRunner interface wrapping the 'gh' CLI to:
- Check if gh is installed and authenticated
- Get the authenticated user's username
- List the user's GitHub organizations
- Probe whether owner/aspire-templates repos exist
GitTemplateIndexService now auto-discovers template sources by probing
{username}/aspire-templates and {org}/aspire-templates repos in parallel.
Controlled via templates.enablePersonalDiscovery and
templates.enableOrgDiscovery config values (both default to true).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Discovered (personal/org) repos were using the official 'release/latest' default ref, which doesn't exist on community repos. Now uses 'HEAD' (resolves to repo's default branch) for non-official sources. Only the official dotnet/aspire source uses 'release/latest'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…upport - Add LocalizableString type for inline culture-specific translations (string or object with culture keys in aspire-template.json) - Add DisplayName/Description to GitTemplateVariable and GitTemplateVariableChoice - Add DisplayName, Scope to GitTemplateManifest - Add Scope to GitTemplateIndexEntry - Implement type-aware prompting in GitTemplate: string→text prompt with regex, boolean→confirm, choice→selection, integer→min/max - Update GitTemplateFactory to filter by scope for GetTemplates/GetInitTemplates - Fix GitTemplate to implement full ITemplate interface (Runtime, SupportsLanguage, etc.) - Fix KnownEmoji usage in template commands after rebase - Update spec with scope and localization documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mplate services The test helper was missing registrations for Commands.Template.*, IGitTemplateIndexService, IGitTemplateEngine, IGitHubCliRunner, and GitTemplateCache that were added to Program.cs by the git templates branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Git template variables can now be provided as --key value pairs on the command line (e.g., aspire new my-template --useRedis true --dbProvider postgres). Variables not provided on the CLI are still prompted interactively. - ApplyOptions sets TreatUnmatchedTokensAsErrors = false to accept passthrough tokens - ParseUnmatchedTokens extracts --key value pairs, bare --flag as true - TryGetCliValue matches both camelCase and kebab-case naming - ValidateCliValue applies type/regex/min-max/choice validation - Updated spec with CLI variable binding documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously GitTemplate silently used the current directory name as the project name and derived the output path. Now it interactively prompts for both values (with sensible defaults) when --name and --output are not provided on the command line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Templates can define postInstructions blocks that are displayed after
template application. Each block has:
- heading: slug line displayed with # prefix styling
- priority: 'primary' (rocket emoji, bold) or 'secondary' (info emoji, dim)
- lines: instruction text with {{variable}} placeholder substitution
- condition: optional 'var == value' or 'var != value' expression
Primary instructions are rendered first, then secondary. Blocks whose
condition evaluates to false are omitted entirely.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduces the 'aspire template test' command that generates the full cartesian product of all variable combinations for a template and applies each one, reporting pass/fail results. Features: - Template resolution from local path, index, or cwd - testValues property on variables for explicit test value control - Automatic fallback: boolean=true/false, choice=all values, etc. - --dry-run to preview combinations without applying - --json for machine-readable output - Descriptive output subdirectories per combination Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When no path argument is provided and no local template files exist, the command now fetches the template index and presents an interactive selection prompt filtered to git templates. Selected templates are cloned to a temp directory, tested, then cleaned up. Changes: - --output is now optional (defaults to current directory) - --name can select from the remote template index - Added FetchAsync to IGitTemplateEngine for cloning templates - Added git clone/sparse-checkout logic to GitTemplateEngine Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Output directories are now named like bob0, bob1, ... bob9 using the template name and a zero-based index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After applying each template variant, rewrites port numbers in launchSettings.json and apphost.run.json files so each variant uses unique ports. Uses deterministic seeding per variant index for reproducibility, and caches port mappings within a variant so the same original port maps to the same replacement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each template subcommand now overrides UpdateNotificationsEnabled directly instead of going through an intermediate base class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… test Documents all recently implemented features: - postInstructions: structured instruction blocks with priority, conditions, and variable substitution - testValues: explicit test values for template test matrix generation - aspire template test command: full documentation with options - Variable field reference table with all properties - scope field on index entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
181 test cases across 8 test files covering:
- TemplateExpressionEvaluatorTests: 26 tests for {{variable}} substitution,
filters (lowercase, uppercase, kebabcase, snakecase, camelcase, pascalcase),
word splitting edge cases, unresolved variables, special characters
- LocalizableStringTests: 14 tests for plain string, localized objects,
culture fallback (exact/parent/first entry), case-insensitive keys,
JSON deserialization of both string and object forms
- GitTemplateManifestSerializationTests: 33 tests for manifest parsing
including all variable types (string/boolean/choice/integer), validation,
testValues, substitutions, conditionalFiles, postMessages, postInstructions,
scope, localized displayName, roundtrip serialization
- GitTemplateIndexSerializationTests: 10 tests for index parsing including
templates, publisher, includes (federation), schema field, roundtrip
- JsonConverterTests: 24 tests for JsonObjectConverter and
JsonObjectListConverter polymorphic deserialization (string/bool/int/null)
- GitTemplateEngineTests: 21 tests for end-to-end template application
including content/filename substitution, filters, conditional files,
binary file handling, excluded dirs, nested structures, postMessages
- GitTemplateLogicTests: 35 tests for condition evaluation (==, !=, truthy),
variable substitution, CLI token parsing, validation, kebab-case conversion
- GitTemplateSchemaCompatibilityTests: 18 tests with real-world template
manifests to ensure backward compatibility as schema evolves
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
89b3199 to
3ae8557
Compare
Description
Introduces the git-based template system for the Aspire CLI. This is an incremental PR that will be built up over multiple phases.
Phase 1 (this commit):
docs/specs/git-templates.mdspecification document describing the full architecture: federated template indexes, per-template manifests, resolution pipeline, application engine, caching, security model, and CLI integration.aspire templatecommand tree behind thegitTemplatesEnabledfeature flag:aspire template list— list available templatesaspire template search <keyword>— search templates by keywordaspire template refresh— refresh template cacheaspire template new [path]— scaffold a new template manifestaspire template new-index [path]— scaffold a new template indexaspire config set features.gitTemplatesEnabled trueThis is not a replacement for
dotnet new. The git-based template system is additive — it provides polyglot-friendly, community-friendly template discovery and application alongside the existing .NET template engine.Checklist