fix(create_project_from_template): accept per-call template_name override

The template name was hardcoded into the binary at startup via
NewCreateProjectFromTemplate("mathias", "template-go-web"), so
generating from a different template (e.g. template-go-agent)
required a code change and restart. The constructor already
parameterised it correctly — the gap was at the tool's input
schema, which never exposed template_name to the caller.

Adds an optional template_name input field. When set, it overrides
the server-configured default for that call only; when omitted,
behavior is unchanged. Template owner stays server-configured —
only the repo name is per-call.

Server-side validation already verifies the resolved template
exists and is marked as a template repo, so no enum constraint
is added — keeps the door open for future templates (go-ml,
go-service, ...) without redeploys.

Adds TestCreateProjectTemplateNameOverride verifying the override
directs both the template lookup and the /generate POST.

Closes #24
This commit is contained in:
Mathias
2026-05-16 23:01:28 +02:00
parent 4c87856aec
commit 103194a11a
2 changed files with 74 additions and 9 deletions

View File

@@ -45,14 +45,15 @@ func NewCreateProjectFromTemplate(c *gitea.Client, a *allowlist.Allowlist, tmplO
func (t *CreateProjectFromTemplate) Descriptor() registry.ToolDescriptor {
return registry.ToolDescriptor{
Name: "create_project_from_template",
Description: "Create a new project repo from the template, applying placeholder substitutions to known files.",
Description: "Create a new project repo from a template, applying placeholder substitutions to known files. Defaults to the server-configured template; pass template_name to override (e.g. template-go-agent).",
InputSchema: json.RawMessage(`{
"type":"object",
"properties":{
"owner":{"type":"string"},
"name":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,38}[a-z0-9]$"},
"description":{"type":"string"},
"private":{"type":"boolean"}
"private":{"type":"boolean"},
"template_name":{"type":"string","description":"Template repo name to generate from. Defaults to the server-configured template."}
},
"required":["owner","name"]
}`),
@@ -60,10 +61,11 @@ func (t *CreateProjectFromTemplate) Descriptor() registry.ToolDescriptor {
}
type createProjectArgs struct {
Owner string `json:"owner"`
Name string `json:"name"`
Description string `json:"description"`
Private bool `json:"private"`
Owner string `json:"owner"`
Name string `json:"name"`
Description string `json:"description"`
Private bool `json:"private"`
TemplateName string `json:"template_name"`
}
type createProjectResult struct {
@@ -91,13 +93,20 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag
return nil, fmt.Errorf("name %q does not match pattern %s: %w", args.Name, nameRe.String(), gitea.ErrValidation)
}
// Resolve template: per-call override takes precedence over the
// server-configured default. Owner stays server-configured.
tmplName := args.TemplateName
if tmplName == "" {
tmplName = t.templateName
}
// Verify template exists and is marked as a template repo.
tmpl, err := t.c.GetRepo(ctx, t.templateOwner, t.templateName)
tmpl, err := t.c.GetRepo(ctx, t.templateOwner, tmplName)
if err != nil {
return nil, fmt.Errorf("template lookup: %w", err)
}
if !tmpl.Template {
return nil, fmt.Errorf("repo %s/%s is not marked as template: %w", t.templateOwner, t.templateName, gitea.ErrValidation)
return nil, fmt.Errorf("repo %s/%s is not marked as template: %w", t.templateOwner, tmplName, gitea.ErrValidation)
}
// Verify destination doesn't already exist.
@@ -108,7 +117,7 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag
}
// Generate repo from template.
newRepo, err := t.c.GenerateFromTemplate(ctx, t.templateOwner, t.templateName, gitea.GenerateFromTemplateArgs{
newRepo, err := t.c.GenerateFromTemplate(ctx, t.templateOwner, tmplName, gitea.GenerateFromTemplateArgs{
Owner: args.Owner,
Name: args.Name,
Description: args.Description,