diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3d1f67c..950f45d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,14 +5,14 @@ }, "metadata": { "description": "Orchestrator skill for RHDH plugin development - onboard, update, and maintain plugins in the Extensions Catalog", - "version": "0.5.0" + "version": "0.5.1" }, "plugins": [ { "name": "rhdh", "source": "./", "description": "Skills for RHDH plugin lifecycle management", - "version": "0.5.0", + "version": "0.5.1", "strict": true } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 758136d..33826cb 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "rhdh", "description": "All-in-one toolkit for Red Hat Developer Hub (RHDH). Covers plugin development, overlay management, environment setup, version compatibility, CI/CD, and RHDH ecosystem navigation.", - "version": "0.5.0", + "version": "0.5.1", "author": { "name": "RHDH Store Manager" }, diff --git a/README.md b/README.md index 940b349..e4b1fa7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,29 @@ Build dynamic plugins from scratch — backend or frontend — and get them depl - **[export](./skills/create-plugin/references/export.md)** — Export, package (OCI/tgz/npm), and push to a container registry. - **[wiring](./skills/create-plugin/references/wiring.md)** — Analyze plugin source and generate `dynamic-plugins.yaml` wiring config. +### Software Templates + +Author RHDH Scaffolder templates with guided workflows — templatize existing codebases, create from scratch, and fix common gotchas. + +- **[rhdh-templates](./skills/rhdh-templates/SKILL.md)** — Interactive authoring and validation for Software Templates. Includes curated reference catalog (official library + AI quickstarts), worked examples (`nodejs-backend`, `java-springboot`) and bundled JSON Schema validation. Sub-commands: + - **[init](./skills/rhdh-templates/references/init.md)** — Check tooling, scaffold template repo layout, optional RHDH connectivity. + - **[templatize](./skills/rhdh-templates/references/templatize.md)** — Convert existing codebase into a parameterized template. + - **[create](./skills/rhdh-templates/references/create.md)** — Guided from-scratch template authoring when no reference code exists. + - **[add-parameter](./skills/rhdh-templates/references/add-parameter.md)** — Add a parameter or parameter group to existing `template.yaml`. + - **[add-step](./skills/rhdh-templates/references/add-step.md)** — Add a scaffolder step to existing `template.yaml`. + - **[add-skeleton](./skills/rhdh-templates/references/add-skeleton.md)** — Add or parameterize skeleton files with Nunjucks. + - **[create-location](./skills/rhdh-templates/references/create-location.md)** — Generate or update root `location.yaml` for catalog registration. + - **[fix-gotchas](./skills/rhdh-templates/references/fix-gotchas.md)** — Auto-fix common RHDH template mistakes (raw/endraw blocks, catalog-info path, etc.). + - **[validate](./skills/rhdh-templates/references/validate.md)** — Local YAML schema, gotcha validation, and optional Nunjucks lint via `--lint-skeleton` (no RHDH required). + - **[list-actions](./skills/rhdh-templates/references/list-actions.md)** — List available Scaffolder actions from a running RHDH instance. + - **[dry-run](./skills/rhdh-templates/references/dry-run.md)** — Test template execution via Scaffolder v2 dry-run API. + - **[explain-action](./skills/rhdh-templates/references/explain-action.md)** — Show action input schema or template parameter schema. + - **[example-catalog](./skills/rhdh-templates/references/example-catalog.md)** — Browse curated reference templates (official library, AI quickstarts, bundled). + +```bash +npx skills add redhat-developer/rhdh-skill --skill rhdh-templates +``` + ### Extensions Catalog Manage plugins in the [rhdh-plugin-export-overlays](https://github.com/redhat-developer/rhdh-plugin-export-overlays) repository. diff --git a/pyproject.toml b/pyproject.toml index bad9a86..726c0d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "rhdh-skill" -version = "0.5.0" +version = "0.5.1" description = "Claude Code skill for RHDH plugin development" readme = "README.md" license = "Apache-2.0" @@ -15,7 +15,8 @@ include = ["rhdh*"] [project.optional-dependencies] dev = [ "pytest>=7.0", - "pyyaml>=6.0", # Only for SKILL.md structure tests + "pyyaml>=6.0", # SKILL.md structure tests and template YAML parsing + "jsonschema>=4.0", # Optional full JSON Schema validation in rhdh-templates "ruff>=0.4.0", ] diff --git a/skills/rhdh-templates/SKILL.md b/skills/rhdh-templates/SKILL.md new file mode 100644 index 0000000..d77cbe6 --- /dev/null +++ b/skills/rhdh-templates/SKILL.md @@ -0,0 +1,139 @@ +--- +name: rhdh-templates +description: >- + Author and validate RHDH Software Templates (Scaffolder) with AI-guided workflows. Use when + asked to "create software template", "templatize a codebase", "convert repo to + template", "write template.yaml", "location.yaml for templates", "scaffolder + template", "golden path template", "parameterize skeleton files", "fix template + gotchas", "validate template", "dry-run template", "list scaffolder actions", + "explain scaffolder action", "Nunjucks in template", "template best practices", + "reference templates", "example templates", "what templates do customers use", + "Template Editor", or mentions RHDH template + authoring, Software Catalog templates, or /rhdh-templates commands. Covers setup, + templatize (highest value), from-scratch create, reference example discovery, + incremental parameter/step/skeleton authoring, location.yaml generation, common + convention fixes, local validation, and live Scaffolder API dry-run/action discovery. +--- + + + +## Domain + +Software Templates are `kind: Template` entities processed by the Scaffolder. Each template has: + +- `template.yaml` — metadata, `spec.parameters` (form), `spec.steps` (actions), `spec.output` +- `skeleton/` — files copied/templated into the target repo (Nunjucks `{% raw %}` blocks where needed) +- `location.yaml` (repo root) — `kind: Location` registering all `template.yaml` files for catalog import + +Read `references/conventions.md` before editing any template artifact — it encodes RHDH-specific rules that differ from generic Backstage docs. + +Read `references/best-practices.md` when authoring or reviewing templates — it encodes Red Hat's 10 tips for repository layout, Template Editor workflow, custom fields, Nunjucks, secrets, type/tags, TechDocs, and maintenance. + +## Authoring stance + +- **Interactive, not fully automatic.** Templatize proposes parameterization; the user confirms each literal-to-parameter mapping. +- **Conservative parameterization.** Under-parameterize rather than expose every string — users can add parameters incrementally. +- **First-try correctness.** Generated artifacts should pass local `validate` with zero critical findings before merge. + +## Script paths + +All `scripts/` and `references/` paths are relative to this SKILL.md directory. Resolve them before invoking. + + + + + +## Setup gates (non-optional before file edits) + +| Gate | Required check | If fail | +|------|----------------|---------| +| Command | Sub-command reference loaded | Load the matching `references/.md` (or `example-catalog.md` for `examples`) | +| Layout | Template project initialized or path confirmed | Run `init` or ask user for template repo root | +| Conventions | `references/conventions.md` read for authoring commands | Read it first | + + + + + +## What would you like to do? + +| # | Command | +|---|---------| +| 1 | `init` | +| 2 | `templatize` | +| 3 | `create` | +| 4 | `add-parameter` | +| 5 | `add-step` | +| 6 | `add-skeleton` | +| 7 | `create-location` | +| 8 | `fix-gotchas` | +| 9 | `validate` | +| 10 | `list-actions` | +| 11 | `dry-run` | +| 12 | `explain-action` | +| 13 | `examples` | + +Command descriptions and argument hints: `scripts/command-metadata.json` + +**Wait for response before proceeding.** + + + + + +| Response | Reference | +|----------|-----------| +| 1, "init", "setup", "scaffold", "prerequisites" | [references/init.md](references/init.md) | +| 2, "templatize", "convert", "parameterize repo", "existing codebase" | [references/templatize.md](references/templatize.md) | +| 3, "create", "from scratch", "new template" | [references/create.md](references/create.md) | +| 4, "add-parameter", "add parameter", "form field" | [references/add-parameter.md](references/add-parameter.md) | +| 5, "add-step", "add step", "scaffolder action", "pipeline step" | [references/add-step.md](references/add-step.md) | +| 6, "add-skeleton", "skeleton file", "nunjucks" | [references/add-skeleton.md](references/add-skeleton.md) | +| 7, "create-location", "location.yaml", "register templates" | [references/create-location.md](references/create-location.md) | +| 8, "fix-gotchas", "fix template", "gotchas", "raw endraw" | [references/fix-gotchas.md](references/fix-gotchas.md) | +| 9, "validate", "lint template", "check template", "lint-nunjucks", "lint nunjucks", "djlint", "nunjucks lint" | [references/validate.md](references/validate.md) | +| 10, "list-actions", "list actions", "scaffolder actions" | [references/list-actions.md](references/list-actions.md) | +| 11, "dry-run", "dry run", "test template remotely" | [references/dry-run.md](references/dry-run.md) | +| 12, "explain-action", "action schema", "parameter schema" | [references/explain-action.md](references/explain-action.md) | +| 13, "examples", "reference templates", "show me templates", "what templates exist" | [references/example-catalog.md](references/example-catalog.md) | +| First word doesn't match | Infer from context. "Turn my Spring Boot app into a template" → `templatize`. "Add owner picker to my template" → `add-parameter`. "Does my template validate?" → `validate`. "What templates do customers use?" → `examples`. | + + + + + +## Bundled scripts + +```bash +# Resolve skill directory (adjust if SKILL.md path differs) +SKILL_DIR="/skills/rhdh-templates" + +python "$SKILL_DIR/scripts/init.py" --help +python "$SKILL_DIR/scripts/analyze.py" --help +python "$SKILL_DIR/scripts/create_location.py" --help +python "$SKILL_DIR/scripts/fix_gotchas.py" --help +python "$SKILL_DIR/scripts/validate.py" --help +python "$SKILL_DIR/scripts/list_actions.py" --help +python "$SKILL_DIR/scripts/dry_run.py" --help +python "$SKILL_DIR/scripts/explain_action.py" --help +python "$SKILL_DIR/scripts/list_examples.py" --help +``` + +Run `init.py` for deterministic tooling checks and project scaffolding. Use `analyze.py` during `templatize` Phase 1. Use `list_examples.py` during `create`, `templatize`, or `examples` to rank upstream reference templates. Use `create_location.py` and `fix_gotchas.py` where the reference files direct you — they produce structured JSON when piped. + +Validation scripts: `validate.py` for local checks (include `--lint-skeleton` for Nunjucks/djLint); `list_actions.py`, `dry_run.py`, and `explain_action.py` require a reachable RHDH `--rhdh-url` and optional bearer token (`RHDH_TOKEN` env or `--token`). + + + + + +| File | Load when... | +|------|-------------| +| `references/conventions.md` | Any authoring command — RHDH template rules | +| `references/best-practices.md` | Authoring/review — Red Hat 10 tips and pre-merge checklist | +| `references/template-structure.md` | Writing or reviewing `template.yaml` anatomy | +| `references/parameter-widgets.md` | Choosing form fields and UI widgets for parameters | +| `references/example-catalog.md` | Command `examples` or picking upstream study references (`assets/example-catalog.json` is the data source; bundled templates under `assets/examples/`) | +| `references/schemas/template-v1beta3.schema.json` | Bundled JSON Schema for deep `validate` checks | + + diff --git a/skills/rhdh-templates/assets/example-catalog.json b/skills/rhdh-templates/assets/example-catalog.json new file mode 100644 index 0000000..e9b4615 --- /dev/null +++ b/skills/rhdh-templates/assets/example-catalog.json @@ -0,0 +1,386 @@ +{ + "version": 1, + "disclaimer": "Upstream templates are learning aids, not production-ready. Validate, test, and customize before use in your organization.", + "sources": [ + { + "id": "rhdh-software-templates", + "repo": "redhat-developer/red-hat-developer-hub-software-templates", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates", + "description": "Official RHDH Software Templates library (registered via templates.yaml)", + "official": true + }, + { + "id": "ai-quickstart-templates", + "repo": "redhat-developer/aiquickstarttemplates", + "url": "https://github.com/redhat-developer/aiquickstarttemplates", + "description": "Red Hat AI Quickstart Backstage templates for OpenShift AI", + "official": true + }, + { + "id": "ai-lab-template", + "repo": "redhat-ai-dev/ai-lab-template", + "url": "https://github.com/redhat-ai-dev/ai-lab-template", + "description": "Podman Desktop AI Lab sample templates (RAG, chatbot, codegen, etc.)", + "official": false + }, + { + "id": "bundled", + "repo": "redhat-developer/rhdh-skill", + "url": "https://github.com/redhat-developer/rhdh-skill/tree/main/skills/rhdh-templates/assets/examples", + "description": "Minimal worked examples shipped with the rhdh-templates skill for local validation", + "official": true + } + ], + "examples": [ + { + "id": "go-backend", + "title": "Create a Go Backend application with a CI pipeline", + "source": "rhdh-software-templates", + "path": "templates/github/go-backend", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/go-backend", + "category": "backend", + "tags": ["recommended", "go", "backend", "ci", "github"], + "stack": ["go"], + "use_cases": ["golden path backend service", "new microservice with pipeline"], + "recommended": true, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "nodejs-backend", + "title": "Create a Node.js Backend application with a CI pipeline", + "source": "rhdh-software-templates", + "path": "templates/github/nodejs-backend", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/nodejs-backend", + "category": "backend", + "tags": ["recommended", "nodejs", "javascript", "typescript", "express", "backend", "ci", "github"], + "stack": ["nodejs"], + "use_cases": ["nodejs api service", "express backend with publish and register"], + "recommended": true, + "in_templates_yaml": true, + "local_bundled": "nodejs-backend" + }, + { + "id": "spring-boot-backend", + "title": "Create a Spring Boot Backend application with a CI pipeline", + "source": "rhdh-software-templates", + "path": "templates/github/spring-boot-backend", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/spring-boot-backend", + "category": "backend", + "tags": ["recommended", "spring-boot", "java", "maven", "backend", "ci", "github"], + "stack": ["java", "spring-boot"], + "use_cases": ["java spring boot service", "maven backend golden path"], + "recommended": true, + "in_templates_yaml": true, + "local_bundled": "java-springboot" + }, + { + "id": "quarkus-backend", + "title": "Create a Quarkus Backend application with a CI pipeline", + "source": "rhdh-software-templates", + "path": "templates/github/quarkus-backend", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/quarkus-backend", + "category": "backend", + "tags": ["quarkus", "java", "maven", "backend", "ci", "github"], + "stack": ["java", "quarkus"], + "use_cases": ["quarkus microservice", "cloud-native java backend"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "python-backend-gitlab", + "title": "Create a Python backend application in GitLab with a CI pipeline", + "source": "rhdh-software-templates", + "path": "templates/gitlab/python-backend", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/gitlab/python-backend", + "category": "backend", + "tags": ["python", "flask", "backend", "ci", "gitlab"], + "stack": ["python"], + "use_cases": ["python api on gitlab", "flask backend service"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "dotnet-frontend", + "title": "Create a .NET Frontend application with a CI pipeline", + "source": "rhdh-software-templates", + "path": "templates/azure/dotnet-frontend", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/azure/dotnet-frontend", + "category": "frontend", + "tags": ["dotnet", "csharp", "frontend", "ci", "azure"], + "stack": ["dotnet"], + "use_cases": ["dotnet web frontend", "azure devops pipeline"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "register-component", + "title": "Register existing component to Software Catalog", + "source": "rhdh-software-templates", + "path": "templates/github/register-component", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/register-component", + "category": "catalog", + "tags": ["recommended", "import", "catalog", "register", "github"], + "stack": [], + "use_cases": ["import existing repo", "onboard legacy service to catalog"], + "recommended": true, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "tekton", + "title": "Create a Tekton CI Pipeline", + "source": "rhdh-software-templates", + "path": "templates/github/tekton", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/tekton", + "category": "cicd", + "tags": ["tekton", "ci", "openshift", "pipeline"], + "stack": ["tekton"], + "use_cases": ["add tekton pipeline to project", "openshift ci"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "argocd", + "title": "Add ArgoCD to an existing project", + "source": "rhdh-software-templates", + "path": "templates/github/argocd", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/argocd", + "category": "gitops", + "tags": ["recommended", "argocd", "gitops", "kubernetes"], + "stack": ["argocd"], + "use_cases": ["gitops bootstrap", "add argocd app to repo"], + "recommended": true, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "define-ansible-job", + "title": "Define an Ansible Job Template within Ansible Automation Platform", + "source": "rhdh-software-templates", + "path": "templates/github/define-ansible-job", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/define-ansible-job", + "category": "automation", + "tags": ["recommended", "ansible", "aap", "automation"], + "stack": ["ansible"], + "use_cases": ["ansible job template", "aap integration"], + "recommended": true, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "launch-ansible-job", + "title": "Launch an Ansible Job within Ansible Automation Platform", + "source": "rhdh-software-templates", + "path": "templates/github/launch-ansible-job", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/launch-ansible-job", + "category": "automation", + "tags": ["ansible", "aap", "automation"], + "stack": ["ansible"], + "use_cases": ["run ansible job from template", "aap workflow trigger"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "techdocs", + "title": "Create a TechDocs sample", + "source": "rhdh-software-templates", + "path": "templates/github/techdocs", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/techdocs", + "category": "docs", + "tags": ["techdocs", "documentation", "mkdocs"], + "stack": ["techdocs"], + "use_cases": ["documentation starter", "techdocs onboarding"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "create-backend-plugin", + "title": "Create Backend Plugin Template", + "source": "rhdh-software-templates", + "path": "templates/create-backend-plugin", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/create-backend-plugin", + "category": "plugin", + "tags": ["backend-plugin", "backstage-plugin", "dynamic-plugin"], + "stack": ["typescript"], + "use_cases": ["scaffold rhdh backend plugin", "plugin golden path"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "create-frontend-plugin", + "title": "Create Frontend Plugin Template", + "source": "rhdh-software-templates", + "path": "templates/create-frontend-plugin", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/create-frontend-plugin", + "category": "plugin", + "tags": ["frontend-plugin", "backstage-plugin", "dynamic-plugin"], + "stack": ["typescript", "react"], + "use_cases": ["scaffold rhdh frontend plugin", "plugin golden path"], + "recommended": false, + "in_templates_yaml": true, + "local_bundled": null + }, + { + "id": "obc", + "title": "Add OBC to an existing project", + "source": "rhdh-software-templates", + "path": "templates/github/obc", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/obc", + "category": "infrastructure", + "tags": ["obc", "openshift", "storage"], + "stack": ["openshift"], + "use_cases": ["object bucket claim", "openshift storage"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "sdlc-app", + "title": "SDLC environments Application", + "source": "rhdh-software-templates", + "path": "templates/github/sdlc-app", + "url": "https://github.com/redhat-developer/red-hat-developer-hub-software-templates/tree/main/templates/github/sdlc-app", + "category": "application", + "tags": ["application", "sdlc", "environments"], + "stack": [], + "use_cases": ["multi-component application", "sdlc environment setup"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "enterprise-rag-chatbot", + "title": "Enterprise RAG Chatbot", + "source": "ai-quickstart-templates", + "path": "templates/enterprise-rag-chatbot", + "url": "https://github.com/redhat-developer/aiquickstarttemplates/tree/main/templates/enterprise-rag-chatbot", + "category": "ai", + "tags": ["ai", "rag", "llm", "chatbot", "vector-db", "openshift-ai"], + "stack": ["python", "ai", "rag"], + "use_cases": ["internal knowledge base", "document q&a chatbot", "enterprise rag"], + "recommended": true, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "it-self-service-agent", + "title": "IT Self-Service Agent", + "source": "ai-quickstart-templates", + "path": "templates/it-self-service-agent", + "url": "https://github.com/redhat-developer/aiquickstarttemplates/tree/main/templates/it-self-service-agent", + "category": "ai", + "tags": ["ai", "agent", "llm", "automation", "openshift-ai"], + "stack": ["python", "ai", "agent"], + "use_cases": ["it automation agent", "self-service helpdesk bot"], + "recommended": true, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "llm-cpu-serving", + "title": "LLM CPU Serving", + "source": "ai-quickstart-templates", + "path": "templates/llm-cpu-serving", + "url": "https://github.com/redhat-developer/aiquickstarttemplates/tree/main/templates/llm-cpu-serving", + "category": "ai", + "tags": ["ai", "llm", "cpu", "inference", "openshift-ai"], + "stack": ["python", "ai", "llm"], + "use_cases": ["lightweight llm endpoint", "dev/test model serving"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "generic-ai-quickstart", + "title": "Generic AI Quickstart", + "source": "ai-quickstart-templates", + "path": "templates/generic-ai-quickstart", + "url": "https://github.com/redhat-developer/aiquickstarttemplates/tree/main/templates/generic-ai-quickstart", + "category": "ai", + "tags": ["ai", "quickstart", "openshift-ai", "flexible"], + "stack": ["python", "ai"], + "use_cases": ["custom ai project bootstrap", "adaptable ai template"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "ai-virtual-agent", + "title": "AI Virtual Agent", + "source": "ai-quickstart-templates", + "path": "templates/ai-virtual-agent", + "url": "https://github.com/redhat-developer/aiquickstarttemplates/tree/main/templates/ai-virtual-agent", + "category": "ai", + "tags": ["ai", "agent", "virtual-agent", "openshift-ai"], + "stack": ["python", "ai", "agent"], + "use_cases": ["virtual assistant deployment", "agentic ai application"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "ai-lab-rag", + "title": "RAG Chatbot Application", + "source": "ai-lab-template", + "path": "templates/rag", + "url": "https://github.com/redhat-ai-dev/ai-lab-template/tree/main/templates/rag", + "category": "ai", + "tags": ["ai", "rag", "chatbot", "llm"], + "stack": ["python", "ai", "rag"], + "use_cases": ["rag chatbot sample", "ai lab starter"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "ai-lab-chatbot", + "title": "Chatbot Application", + "source": "ai-lab-template", + "path": "templates/chatbot", + "url": "https://github.com/redhat-ai-dev/ai-lab-template/tree/main/templates/chatbot", + "category": "ai", + "tags": ["ai", "chatbot", "llm"], + "stack": ["python", "ai"], + "use_cases": ["simple chatbot sample", "ai lab starter"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "ai-lab-codegen", + "title": "Codegen Application", + "source": "ai-lab-template", + "path": "templates/codegen", + "url": "https://github.com/redhat-ai-dev/ai-lab-template/tree/main/templates/codegen", + "category": "ai", + "tags": ["ai", "codegen", "llm"], + "stack": ["python", "ai"], + "use_cases": ["code generation sample", "developer assistant"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": null + }, + { + "id": "minimal-template", + "title": "Minimal starter template (bundled)", + "source": "bundled", + "path": "assets/examples/minimal-template", + "url": "https://github.com/redhat-developer/rhdh-skill/tree/main/skills/rhdh-templates/assets/examples/minimal-template", + "category": "starter", + "tags": ["starter", "minimal", "validation"], + "stack": [], + "use_cases": ["learn template structure", "validate locally without network"], + "recommended": false, + "in_templates_yaml": false, + "local_bundled": "minimal-template" + } + ] +} diff --git a/skills/rhdh-templates/assets/examples/README.md b/skills/rhdh-templates/assets/examples/README.md new file mode 100644 index 0000000..5d16bd7 --- /dev/null +++ b/skills/rhdh-templates/assets/examples/README.md @@ -0,0 +1,29 @@ +# RHDH Templates Examples + +Bundled worked examples for **local learning and validation**. Each passes `validate.py` with zero critical findings. + +For the full curated catalog of upstream reference templates (official library, AI quickstarts), run: + +```bash +python skills/rhdh-templates/scripts/list_examples.py --recommended --json +``` + +See [../references/example-catalog.md](../references/example-catalog.md) for category guide and customer-demand context. + +| Example | Stack | Highlights | +|---------|-------|------------| +| [minimal-template](./minimal-template/) | Generic | Starter scaffold from `init` — single parameter form | +| [nodejs-backend](./nodejs-backend/) | Node.js | `EntityPicker`, `RepoUrlPicker`, publish + register, GitHub Actions `{% raw %}` | +| [java-springboot](./java-springboot/) | Java / Spring Boot | Maven `pom.xml`, `Application.java`, multi-section forms | + +These bundled examples correspond to upstream references in [red-hat-developer-hub-software-templates](https://github.com/redhat-developer/red-hat-developer-hub-software-templates): `nodejs-backend` → `templates/github/nodejs-backend`, `java-springboot` → `templates/github/spring-boot-backend`. + +Validate any example locally: + +```bash +python skills/rhdh-templates/scripts/validate.py \ + --path skills/rhdh-templates/assets/examples/nodejs-backend \ + --repo --lint-skeleton --json +``` + +Replace `nodejs-backend` with `java-springboot` or `minimal-template` as needed. diff --git a/skills/rhdh-templates/assets/examples/java-springboot/README.md b/skills/rhdh-templates/assets/examples/java-springboot/README.md new file mode 100644 index 0000000..cb54c5a --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/README.md @@ -0,0 +1,24 @@ +# Java Spring Boot Service + +Scaffolds a minimal Spring Boot 3 service with: + +- Maven `pom.xml` parameterized by component ID and Java version +- `Application.java` and `application.properties` +- `catalog-info.yaml` for Software Catalog registration + +## Parameters + +| Parameter | Purpose | +|-----------|---------| +| `componentId` | Catalog entity name and Maven artifact ID | +| `description` | Shown in catalog and repository | +| `owner` | Catalog owner entity ref | +| `javaVersion` | Java LTS version (`17` or `21`) | +| `packageName` | Java base package for generated sources | +| `repoUrl` | Target GitHub repository | + +## Post-scaffold steps + +1. Run `./mvnw spring-boot:run` locally +2. Confirm CI passes after first push +3. Verify catalog registration in RHDH diff --git a/skills/rhdh-templates/assets/examples/java-springboot/skeleton/README.md b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/README.md new file mode 100644 index 0000000..ad34bdd --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/README.md @@ -0,0 +1,13 @@ +# {{ values.componentId }} + +{{ values.description }} + +Owner: {{ values.owner }} + +## Development + +```bash +./mvnw spring-boot:run +``` + +Uses Java {{ values.javaVersion }} and package `{{ values.packageName }}`. diff --git a/skills/rhdh-templates/assets/examples/java-springboot/skeleton/catalog-info.yaml b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/catalog-info.yaml new file mode 100644 index 0000000..acea76f --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{ values.componentId }} + description: {{ values.description }} +spec: + type: service + lifecycle: experimental + owner: {{ values.owner }} diff --git a/skills/rhdh-templates/assets/examples/java-springboot/skeleton/pom.xml b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/pom.xml new file mode 100644 index 0000000..7946fc2 --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.4.0 + + + + {{ values.packageName }} + {{ values.componentId }} + 0.0.1-SNAPSHOT + {{ values.componentId }} + {{ values.description }} + + + {{ values.javaVersion }} + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/skills/rhdh-templates/assets/examples/java-springboot/skeleton/src/main/java/com/example/demo/Application.java b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/src/main/java/com/example/demo/Application.java new file mode 100644 index 0000000..4973964 --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/src/main/java/com/example/demo/Application.java @@ -0,0 +1,22 @@ +package {{ values.packageName }}; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @RestController + static class HelloController { + @GetMapping("/") + public String hello() { + return "{{ values.componentId }} is running"; + } + } +} diff --git a/skills/rhdh-templates/assets/examples/java-springboot/skeleton/src/main/resources/application.properties b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/src/main/resources/application.properties new file mode 100644 index 0000000..3a3779e --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/skeleton/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name={{ values.componentId }} +server.port=8080 diff --git a/skills/rhdh-templates/assets/examples/java-springboot/template.yaml b/skills/rhdh-templates/assets/examples/java-springboot/template.yaml new file mode 100644 index 0000000..1c98baa --- /dev/null +++ b/skills/rhdh-templates/assets/examples/java-springboot/template.yaml @@ -0,0 +1,101 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: java-springboot + title: Java Spring Boot Service + description: Scaffold a Spring Boot microservice with Maven, catalog registration, and GitHub publish + tags: + - recommended + - java + - spring-boot + - microservice +spec: + owner: group:default/platform-team + type: service + + parameters: + - title: Component details + required: + - componentId + - owner + - description + - javaVersion + properties: + componentId: + title: Component ID + type: string + description: Unique ID used for artifact name and catalog entity + pattern: '^[a-z0-9-]*[a-z0-9]$' + ui:autofocus: true + description: + title: Description + type: string + description: What this service does + owner: + title: Owner + type: string + ui:field: EntityPicker + ui:options: + catalogFilter: + kind: + - Group + - User + javaVersion: + title: Java version + type: string + enum: + - "17" + - "21" + default: "21" + packageName: + title: Java package + type: string + description: Base package (e.g. com.example.demo) + pattern: '^[a-z][a-z0-9.]*$' + - title: Repository location + required: + - repoUrl + properties: + repoUrl: + title: Repository location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + + steps: + - id: fetch-base + name: Fetch skeleton + action: fetch:template + input: + url: ./skeleton + values: + componentId: ${{ parameters.componentId }} + description: ${{ parameters.description }} + owner: ${{ parameters.owner }} + javaVersion: ${{ parameters.javaVersion }} + packageName: ${{ parameters.packageName }} + + - id: publish + name: Publish to GitHub + action: publish:github + input: + repoUrl: ${{ parameters.repoUrl }} + description: ${{ parameters.description }} + defaultBranch: main + + - id: register + name: Register in catalog + action: catalog:register + input: + repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} + catalogInfoPath: /catalog-info.yaml + + output: + links: + - title: Open repository + url: ${{ steps.publish.output.remoteUrl }} + - title: View in catalog + icon: catalog + entityRef: ${{ steps.register.output.entityRef }} diff --git a/skills/rhdh-templates/assets/examples/minimal-template/skeleton/README.md b/skills/rhdh-templates/assets/examples/minimal-template/skeleton/README.md new file mode 100644 index 0000000..6553204 --- /dev/null +++ b/skills/rhdh-templates/assets/examples/minimal-template/skeleton/README.md @@ -0,0 +1,5 @@ +# {{ values.componentId }} + +{{ values.description }} + +Owner: {{ values.owner }} diff --git a/skills/rhdh-templates/assets/examples/minimal-template/skeleton/catalog-info.yaml b/skills/rhdh-templates/assets/examples/minimal-template/skeleton/catalog-info.yaml new file mode 100644 index 0000000..acea76f --- /dev/null +++ b/skills/rhdh-templates/assets/examples/minimal-template/skeleton/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{ values.componentId }} + description: {{ values.description }} +spec: + type: service + lifecycle: experimental + owner: {{ values.owner }} diff --git a/skills/rhdh-templates/assets/examples/minimal-template/template.yaml b/skills/rhdh-templates/assets/examples/minimal-template/template.yaml new file mode 100644 index 0000000..29f1eac --- /dev/null +++ b/skills/rhdh-templates/assets/examples/minimal-template/template.yaml @@ -0,0 +1,54 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: minimal-example + title: Minimal Example Template + description: Starter template scaffolded by rhdh-templates init + tags: + - example +spec: + owner: group:default/platform-team + type: service + + parameters: + - title: Component details + required: + - componentId + - owner + properties: + componentId: + title: Component ID + type: string + pattern: '^[a-z0-9-]*[a-z0-9]$' + description: Unique ID for the new component + ui:help: Lowercase letters, digits, and dashes only + ui:autofocus: true + description: + title: Description + type: string + description: What this component does + owner: + title: Owner + type: string + ui:field: EntityPicker + ui:options: + catalogFilter: + kind: + - Group + - User + + steps: + - id: fetch-base + name: Fetch skeleton + action: fetch:template + input: + url: ./skeleton + values: + componentId: ${{ parameters.componentId }} + description: ${{ parameters.description }} + owner: ${{ parameters.owner }} + + output: + links: + - title: Template output + url: https://backstage.io/docs/features/software-templates/ diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/README.md b/skills/rhdh-templates/assets/examples/nodejs-backend/README.md new file mode 100644 index 0000000..afca09d --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/README.md @@ -0,0 +1,23 @@ +# Node.js Backend Service + +Scaffolds a minimal Express-style Node.js service with: + +- `package.json` parameterized by component ID and Node version +- `catalog-info.yaml` for Software Catalog registration +- GitHub Actions CI workflow (with `{% raw %}` for Actions syntax) + +## Parameters + +| Parameter | Purpose | +|-----------|---------| +| `componentId` | Catalog entity name and npm package name | +| `description` | Shown in catalog and repository | +| `owner` | Catalog owner entity ref | +| `nodeVersion` | Node.js LTS version (`20` or `22`) | +| `repoUrl` | Target GitHub repository | + +## Post-scaffold steps + +1. Run `npm install` in the new repository +2. Push triggers CI via GitHub Actions +3. Confirm the component appears in the Software Catalog diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/.github/workflows/ci.yaml b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/.github/workflows/ci.yaml new file mode 100644 index 0000000..88fcd3b --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/.github/workflows/ci.yaml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: +{% raw %} + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm install + - run: npm test +{% endraw %} diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/README.md b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/README.md new file mode 100644 index 0000000..976d4de --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/README.md @@ -0,0 +1,14 @@ +# {{ values.componentId }} + +{{ values.description }} + +Owner: {{ values.owner }} + +## Development + +```bash +npm install +npm start +``` + +Runs on Node.js {{ values.nodeVersion }}. diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/catalog-info.yaml b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/catalog-info.yaml new file mode 100644 index 0000000..acea76f --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{ values.componentId }} + description: {{ values.description }} +spec: + type: service + lifecycle: experimental + owner: {{ values.owner }} diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/package.json b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/package.json new file mode 100644 index 0000000..794ac07 --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/package.json @@ -0,0 +1,14 @@ +{ + "name": "{{ values.componentId }}", + "version": "0.1.0", + "private": true, + "description": "{{ values.description }}", + "main": "src/index.js", + "engines": { + "node": ">={{ values.nodeVersion }}.0.0" + }, + "scripts": { + "start": "node src/index.js", + "test": "node --test" + } +} diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/src/index.js b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/src/index.js new file mode 100644 index 0000000..946c96a --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/skeleton/src/index.js @@ -0,0 +1,12 @@ +const http = require('http'); + +const port = process.env.PORT || 3000; + +const server = http.createServer((_req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ service: '{{ values.componentId }}', status: 'ok' })); +}); + +server.listen(port, () => { + console.log(`{{ values.componentId }} listening on port ${port}`); +}); diff --git a/skills/rhdh-templates/assets/examples/nodejs-backend/template.yaml b/skills/rhdh-templates/assets/examples/nodejs-backend/template.yaml new file mode 100644 index 0000000..2a38035 --- /dev/null +++ b/skills/rhdh-templates/assets/examples/nodejs-backend/template.yaml @@ -0,0 +1,93 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: nodejs-backend + title: Node.js Backend Service + description: Scaffold a Node.js REST API with catalog registration and GitHub publish + tags: + - recommended + - nodejs + - backend +spec: + owner: group:default/platform-team + type: service + + parameters: + - title: Component details + required: + - componentId + - owner + - description + properties: + componentId: + title: Component ID + type: string + description: Unique ID for the new component + pattern: '^[a-z0-9-]*[a-z0-9]$' + ui:autofocus: true + description: + title: Description + type: string + description: What this service does + owner: + title: Owner + type: string + ui:field: EntityPicker + ui:options: + catalogFilter: + kind: + - Group + - User + nodeVersion: + title: Node.js version + type: string + enum: + - "20" + - "22" + default: "20" + - title: Repository location + required: + - repoUrl + properties: + repoUrl: + title: Repository location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + + steps: + - id: fetch-base + name: Fetch skeleton + action: fetch:template + input: + url: ./skeleton + values: + componentId: ${{ parameters.componentId }} + description: ${{ parameters.description }} + owner: ${{ parameters.owner }} + nodeVersion: ${{ parameters.nodeVersion }} + + - id: publish + name: Publish to GitHub + action: publish:github + input: + repoUrl: ${{ parameters.repoUrl }} + description: ${{ parameters.description }} + defaultBranch: main + + - id: register + name: Register in catalog + action: catalog:register + input: + repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} + catalogInfoPath: /catalog-info.yaml + + output: + links: + - title: Open repository + url: ${{ steps.publish.output.remoteUrl }} + - title: View in catalog + icon: catalog + entityRef: ${{ steps.register.output.entityRef }} diff --git a/skills/rhdh-templates/references/add-parameter.md b/skills/rhdh-templates/references/add-parameter.md new file mode 100644 index 0000000..5e9e37d --- /dev/null +++ b/skills/rhdh-templates/references/add-parameter.md @@ -0,0 +1,74 @@ +# add-parameter — Incremental Parameter Authoring + + + +- `conventions.md` +- `template-structure.md` +- `parameter-widgets.md` + + + + + +Extend an existing `template.yaml` without re-running full `templatize` or `create`. + +## Step 1: Locate template + +Confirm path to `templates//template.yaml`. + +## Step 2: Define parameter + +Gather from user: + +| Field | Notes | +|-------|-------| +| Name | camelCase key (e.g., `repoName`) | +| Title | Form label | +| Type | `string`, `number`, `boolean`, `array` | +| Widget | default, `EntityPicker`, `RepoUrlPicker`, `radio`, etc. | +| Section | existing `parameters[].title` or new section | +| Required | yes/no | + +## Step 3: Edit template.yaml + +Add to appropriate `parameters` section: + +```yaml +repoName: + title: Repository Name + type: string + description: GitHub repository name for the new component + ui:autofocus: true +``` + +For conditional fields, add `dependencies` block per `template-structure.md`. + +## Step 4: Wire into steps + +Every new parameter used in skeleton or actions must appear in a `fetch:template` `values` map: + +```yaml +values: + repoName: ${{ parameters.repoName }} +``` + +Search all `steps[].input` for missing wiring after the edit. + +## Step 5: Update skeleton (if needed) + +If the parameter replaces a literal in skeleton files, update Nunjucks to `{{ values. }}`. + +## Step 6: Verify + +Run `fix_gotchas.py` on the template path. Confirm parameter appears in form and values map. + + + + + +- Parameter added to correct form section with type and UI field +- All `fetch:template` steps pass the parameter in `values` +- Skeleton references updated when parameter replaces literals +- No duplicate parameter keys + + diff --git a/skills/rhdh-templates/references/add-skeleton.md b/skills/rhdh-templates/references/add-skeleton.md new file mode 100644 index 0000000..a1af475 --- /dev/null +++ b/skills/rhdh-templates/references/add-skeleton.md @@ -0,0 +1,87 @@ +# add-skeleton — Incremental Skeleton Authoring + + + +- `conventions.md` + + + + + +Add or extend files under `templates//skeleton/` for an existing template. + +## Step 1: Confirm template context + +Locate `templates//template.yaml` and existing `skeleton/` tree. + +## Step 2: Determine file role + +| File type | Templating approach | +|-----------|---------------------| +| App source, README, YAML config | Nunjucks `{{ values.* }}` | +| GitHub Actions, Helm with `{{` | `{% raw %}` … `{% endraw %}` OR `copyWithoutTemplating` | +| Binary / images | Do not template — document manual copy | + +## Step 3: Add file + +Create file under `skeleton/` mirroring target repo layout. + +Example `skeleton/catalog-info.yaml`: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{ values.componentId }} + description: {{ values.description }} +spec: + type: service + lifecycle: experimental + owner: {{ values.owner }} +``` + +## Step 4: Sync template.yaml + +1. Ensure parameters exist for every `values.*` key used. +2. Update `fetch:template` step `values` map. +3. If adding CI workflows, set `copyWithoutTemplating` or raw blocks: + +```yaml +input: + url: ./skeleton + copyWithoutTemplating: + - .github/workflows/ +``` + +## Step 5: Optional additional fetch step + +When skeleton has CI overlay from shared path (RHDH software-templates pattern): + +```yaml +- id: ci-template + name: Add CI skeleton + action: fetch:template + input: + url: ${{ parameters.ci }} + copyWithoutTemplating: + - .github/workflows/ + values: + repoName: ${{ parameters.repoName }} +``` + +## Step 6: Verify + +- Grep skeleton for `parameters.` — should be **zero** matches (use `values.` only). +- Grep for unwrapped `{{` in workflow files — should be inside `{% raw %}` or excluded via `copyWithoutTemplating`. +- Run `validate --lint-skeleton` for Nunjucks syntax checks (see `validate.md`). + + + + + +- New skeleton files use `values.*` references only +- Workflow/chart files protected from accidental Nunjucks processing +- `fetch:template` `values` map includes all new placeholders +- File paths match expected output repo structure + + diff --git a/skills/rhdh-templates/references/add-step.md b/skills/rhdh-templates/references/add-step.md new file mode 100644 index 0000000..7b1adf6 --- /dev/null +++ b/skills/rhdh-templates/references/add-step.md @@ -0,0 +1,80 @@ +# add-step — Incremental Step Authoring + + + +- `conventions.md` +- `template-structure.md` + + + + + +Add a scaffolder step to an existing template without rebuilding from scratch. + +## Step 1: Identify action + +Ask user what the step should do. Map to a scaffolder action: + +| Intent | Typical action | +|--------|----------------| +| Copy/template files | `fetch:template` | +| Fetch plain files | `fetch:plain` | +| Publish to GitHub | `publish:github` | +| Register catalog entity | `catalog:register` | +| Run custom action | `custom:` | + +Action IDs are camelCase. When unsure of installed actions, use the `list-actions` command to query the live instance. + +## Step 2: Choose position + +Steps run **in series**. Ask where to insert: + +- Before publish (materialize content) +- After publish (register, notify, trigger CI) + +Assign unique `id` (kebab-case) and human-readable `name`. + +## Step 3: Build input + +Reference parameters and prior step outputs: + +```yaml +- id: notify-team + name: Notify platform team + action: notification:send + input: + recipients: entity:group:default/platform-team + title: New component ${{ parameters.componentId }} + info: ${{ steps.publish.output.remoteUrl }} +``` + +## Step 4: Update output (if needed) + +If the step produces user-facing results, add `spec.output.links` referencing `${{ steps..output.* }}`. + +## Step 5: Verify wiring + +Checklist: + +- [ ] `id` unique among all steps +- [ ] `action` uses correct camelCase ID +- [ ] All `${{ parameters.* }}` exist in form +- [ ] All `${{ steps.*.output.* }}` reference prior step IDs +- [ ] `fetch:template` steps include complete `values` map + +## Step 6: fix-gotchas + +```bash +python /scripts/fix_gotchas.py --path [--apply] [--json] +``` + + + + + +- New step inserted at correct position with unique `id` +- Action ID and inputs match conventions.md +- Downstream steps and `output` updated if they depend on new step +- fix-gotchas reports no critical action-casing or expression errors + + diff --git a/skills/rhdh-templates/references/best-practices.md b/skills/rhdh-templates/references/best-practices.md new file mode 100644 index 0000000..4f0db06 --- /dev/null +++ b/skills/rhdh-templates/references/best-practices.md @@ -0,0 +1,150 @@ +# Backstage Software Template Best Practices + + + +Load when authoring, reviewing, or improving templates — especially during `create`, `templatize`, `add-parameter`, `add-skeleton`, and pre-merge `validate`. + +Source: [10 tips for better Backstage Software Templates](https://developers.redhat.com/articles/2025/03/17/10-tips-better-backstage-software-templates) (Red Hat Developer, 2025). + + + + + +## 1. Structure your template repository + +Use a **central template repository** with one folder per template and a root `location.yaml` that registers all templates via glob — new templates appear in the catalog after commit without per-template import. + +**Why:** Reduces operational toil — platform engineers commit a folder; catalog sync picks it up. Split into multiple repos only when authorship or access boundaries require it. + +**RHDH convention:** See `conventions.md` for the `./templates/**/template.yaml` layout, `location.yaml` pattern, and catalog registration via the location URL (not individual template files). + +Reference implementations: [backstage/software-templates](https://github.com/backstage/software-templates/), [rhdh-demo-gh/templates](https://github.com/rhdh-demo-gh/templates/). + +## 2. Experiment with the Template Editor + +Avoid the slow loop of push → wait for catalog sync → test run. Use Backstage's **Template Editor** at `/create/edit` (or **Template Editor** link on the Software Templates page) to: + +- Edit templates from a local directory or paste YAML +- Preview form rendering live on the right +- Experiment with custom field extensions before committing + +**When to use:** After local `validate` passes, paste into Template Editor for form UX review. Copy final YAML back to the repo. + +**Skill workflow:** `validate` locally first → Template Editor for UX → `dry-run` against live RHDH for execution. + +## 3. Explore installed actions + +Action IDs and schemas vary by RHDH instance (installed plugins). Never guess action names from docs alone. + +| Method | Path / command | +|--------|----------------| +| RHDH UI | Software Catalog → **Installed Actions**, or `/create/actions` | +| Skill CLI | `list-actions --rhdh-url …` | +| Single action schema | `explain-action --action-id …` | + +Use the exact `id` string from the live instance in `steps[].action`. Plugin actions follow `namespace:actionName` (e.g., `quay:create-repository`). + +## 4. Improve DevEx with custom field extensions + +Forms use [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form). Built-in **Custom Field Extensions** reduce errors vs free-text entry: + +| Field | Use when | +|-------|----------| +| `EntityPicker` | Owner, system, component, domain — catalog-backed selection | +| `RepoUrlPicker` | SCM URL with host/org validation | +| `OwnerPicker` | Shortcut for owner groups | +| `Secret` | Passwords, tokens, API keys (see tip 7) | + +Set `allowArbitraryValues: false` when the value must resolve to a catalog entity. See `parameter-widgets.md` for wiring patterns and examples. + +## 5. Process structured data with template filters + +[Template filters](https://backstage.io/docs/features/software-templates/template-extensions) transform values in step expressions. Entity pickers return string refs like `component:default/my-service`. + +```yaml +title: "Deploy ${{ parameters.component | parseEntityRef | pick('name') }}" +targetPath: ./${{ parameters.component | parseEntityRef | pick('name') }} +``` + +Common filters: `parseEntityRef`, `pick`, `json`, `replace`. Use filters in step `input` and `output` — not in skeleton Nunjucks (skeleton uses `values.*` from `fetch:template`; see `conventions.md`). + +## 6. Use the Nunjucks API in skeletons + +`fetch:template` processes skeleton files with [Nunjucks](https://mozilla.github.io/nunjucks/templating.html). Pass data via the step `values` map; reference as `{{ values.name }}` in skeleton files. + +**Tags beyond substitution:** + +| Tag | Use when | +|-----|----------| +| `{% if %}…{% endif %}` | Conditional files or sections based on parameters | +| `{% for %}…{% endfor %}` | Iterate arrays passed in `values` | +| `{% raw %}…{% endraw %}` | Preserve literal `{{` / `{%` (GitHub Actions, Helm) | + +GitHub Actions and Helm files need `{% raw %}` or `copyWithoutTemplating` — see `conventions.md`, `template-structure.md`, and `add-skeleton.md`. `fix-gotchas` and `validate --lint-skeleton` detect common mistakes. + +## 7. Protect secrets + +Never collect credentials with a plain `type: string` text field. Use Backstage's **Secret** field — see `parameter-widgets.md` for the form definition. + +Secrets are masked in the form, review screen, and logs. In steps, reference integration secrets (e.g. `${{ secrets.user.github.token }}`) — never hardcode tokens. Exact secret paths depend on configured integrations; confirm against your RHDH instance. `fix-gotchas` flags obvious hardcoded tokens in step inputs. + +## 8. Specify template type and tags + +`spec.type` is required but often left as generic `service`. Set a meaningful type (`website`, `microservice`, `library`, `infrastructure`) so the Create UI groups templates. Add `metadata.tags` for subcategory filtering. + +See `template-structure.md` for `metadata` and `spec.type` fields. Use the `recommended` tag to highlight golden paths. + +**Why:** The Software Templates page becomes unusable at scale without type/tag filters. + +## 9. Document your templates + +Self-service templates need docs beyond `description`. Two levels: + +**Human README** — `templates//README.md` with purpose, parameters, post-scaffold steps. + +**TechDocs** (when RHDH has TechDocs configured): add `backstage.io/techdocs-ref: dir:.` annotation and `mkdocs.yml` beside `template.yaml`. See `template-structure.md` for the annotation pattern. + +Documented templates show a **View TechDocs** link in the Create UI. Example: [rhdh-demo-gh/templates/deploy-component](https://github.com/rhdh-demo-gh/templates/tree/main/deploy-component). + +## 10. Plan for maintenance + +Templates codify best practices — outdated templates undermine trust. Treat template repos like application code: + +| Practice | How the skill supports it | +|----------|---------------------------| +| Keep skeleton dependencies current | Re-run `templatize` when source repos change | +| Automated regression | `dry-run` via Scaffolder HTTP API after changes | +| Pre-merge checks | `validate` + `fix-gotchas` with zero critical findings | +| Periodic review | Schedule dependency bumps in skeleton `package.json`, Dockerfiles, CI versions | + +**Failure modes to avoid:** Scaffolded apps that fail on first build, frameworks with known CVEs, broken publish/register wiring after SCM API changes. + +## Bonus: Accelerate the development loop + +Shrink feedback time with local RHDH: + +| Approach | When | +|----------|------| +| **rhdh-local** skill | Fast local RHDH for plugin/template testing | +| Template Editor | Form UX without catalog sync | +| `validate --lint-skeleton` | Nunjucks/skeleton checks without RHDH | +| `dry-run` | End-to-end step execution against live instance | + +Recommended loop: `init` → author → `validate` → Template Editor → `dry-run` → commit. + + + + + +Before merge, confirm: + +- [ ] Repo follows central `location.yaml` + `templates//` layout +- [ ] `spec.type` and `metadata.tags` set for discoverability +- [ ] EntityPicker / RepoUrlPicker / Secret used instead of free-text where appropriate +- [ ] Skeleton uses `values.*`; workflows use `{% raw %}` or `copyWithoutTemplating` +- [ ] Sensitive inputs use `ui:field: Secret`; step tokens use `${{ secrets.* }}` +- [ ] README or TechDocs present for non-trivial templates +- [ ] `validate.py` reports zero critical findings +- [ ] Template tested in Template Editor or via `dry-run` + + diff --git a/skills/rhdh-templates/references/conventions.md b/skills/rhdh-templates/references/conventions.md new file mode 100644 index 0000000..0c654c5 --- /dev/null +++ b/skills/rhdh-templates/references/conventions.md @@ -0,0 +1,115 @@ +# RHDH Software Template Conventions + + + +Load this file before editing `template.yaml`, skeleton files, or `location.yaml`. + + + + + +## API versions + +| Artifact | apiVersion | kind | +|----------|------------|------| +| `template.yaml` | `scaffolder.backstage.io/v1beta3` | `Template` | +| `location.yaml` | `backstage.io/v1alpha1` | `Location` | +| `catalog-info.yaml` (in skeleton) | `backstage.io/v1alpha1` | `Component` (typical) | + +Use v1beta3 for new templates — it uses `${{ }}` step expressions. Do not mix v1beta2 `{{ }}` syntax in the same template. + +## Action IDs + +Scaffolder actions use **camelCase** IDs: + +| Correct | Wrong | +|---------|-------| +| `fetch:template` | `fetch:template` with wrong casing in docs only — verify against live instance | +| `publish:github` | `publish:GitHub` | +| `catalog:register` | `catalog:Register` | + +When unsure, list actions from a running RHDH instance with the `list-actions` command. + +## Parameter form conventions + +- Group related fields under `parameters[].title` sections (e.g., "Provide information about the new component"). +- Use `ui:field: EntityPicker` with `catalogFilter.kind` for owner/system pickers. +- Use `ui:field: RepoUrlPicker` with `allowedHosts` for repo URL parameters. +- Use `pattern` + `ui:help` for constrained IDs (see `assets/examples/minimal-template/template.yaml`). + +## Skeleton templating + +Skeleton files use Nunjucks with **values from `fetch:template` steps**, not `parameters` directly: + +```yaml +# template.yaml step +action: fetch:template +input: + url: ./skeleton + values: + repoName: ${{ parameters.repoName }} +``` + +```text +# skeleton/README.md +# Project: {{ values.repoName }} +``` + +### When to use `{% raw %}` … `{% endraw %}` + +Wrap content that must pass through unchanged and contains `{{` or `{%` — common in: + +- GitHub Actions workflows (`.github/workflows/*.yaml`) +- Helm charts with Go templates +- Any file where braces are literal syntax, not Nunjucks + +## Secrets in templates + +Use Backstage/RHDH secrets syntax in step inputs — never hardcode tokens in skeleton files: + +```yaml +token: ${{ secrets.user.github.token }} +``` + +Exact secret paths depend on configured integrations; confirm against your RHDH instance. + +## Repository layout + +``` +template-repo/ +├── location.yaml # kind: Location — registers all templates +└── templates/ + └── my-template/ + ├── template.yaml + ├── skeleton/ # optional README, catalog-info.yaml, app source + └── README.md # optional human docs +``` + +## location.yaml pattern + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Location +metadata: + name: my-org-templates + description: Software Templates for My Org +spec: + targets: + - ./templates/**/template.yaml +``` + +Register the **location.yaml URL** in RHDH (Catalog Import or `catalog.locations` in app-config), not individual template files. + +For repository layout rationale and multi-repo splitting guidance, see `best-practices.md` tip 1. + +## Common publish + register sequence + +Most service templates end with: + +1. `fetch:template` — materialize skeleton +2. `publish:github` (or `publish:gitlab`, etc.) — push to remote +3. `catalog:register` — register `catalog-info.yaml` from the new repo + +Wire `repoContentsUrl` from publish output into register input. + + diff --git a/skills/rhdh-templates/references/create-location.md b/skills/rhdh-templates/references/create-location.md new file mode 100644 index 0000000..b76726a --- /dev/null +++ b/skills/rhdh-templates/references/create-location.md @@ -0,0 +1,70 @@ +# create-location — Generate location.yaml + + + +- `conventions.md` + + + + + +Standalone utility for generating or refreshing root `location.yaml`. Templatize and create flows may also produce this file — use this command when templates were added manually or the glob target is stale. + +## Step 1: Confirm repo root + +The template repository root contains `templates/` with one or more `template.yaml` files. + +## Step 2: Run script + +```bash +python /scripts/create_location.py \ + --path \ + --name \ + [--description "Human description"] \ + [--json] +``` + +| Flag | Default | +|------|---------| +| `--path` | current directory | +| `--name` | derived from directory name + `-templates` suffix | +| `--description` | auto-generated | + +The script: + +1. Discovers `templates/**/template.yaml` +2. Writes or updates `location.yaml` at repo root with glob target `./templates/**/template.yaml` +3. Lists discovered templates in JSON output + +## Step 3: Review output + +Show user the generated `location.yaml` and template count. + +If zero templates found, stop — run `init` or `create` first. + +## Step 4: Registration reminder + +Tell user to register **the location.yaml URL** in RHDH: + +- Catalog Import UI: `/catalog-import` +- Or `catalog.locations` in app-config: + +```yaml +catalog: + locations: + - type: url + target: https://github.com/acme-corp/templates/blob/main/location.yaml + rules: + - allow: [Location, Template] +``` + + + + + +- `location.yaml` exists at repo root with `kind: Location` +- `spec.targets` includes `./templates/**/template.yaml` +- Script JSON reports all discovered template paths +- User knows how to register the Location in RHDH + + diff --git a/skills/rhdh-templates/references/create.md b/skills/rhdh-templates/references/create.md new file mode 100644 index 0000000..1576042 --- /dev/null +++ b/skills/rhdh-templates/references/create.md @@ -0,0 +1,122 @@ +# create — From-Scratch Template + + + +- `conventions.md` +- `template-structure.md` +- `parameter-widgets.md` +- `assets/examples/minimal-template/template.yaml` +- `assets/examples/nodejs-backend/template.yaml` — full publish/register pipeline +- `assets/examples/java-springboot/template.yaml` — Spring Boot + Maven + + + + + +Use when **no reference codebase** exists. For converting existing code, use `templatize` instead. + +## Step 1: Gather intent + +Ask (one round, not necessarily one question each): + +1. **Template purpose** — what golden path does this enable? +2. **Target type** — `service`, `website`, `library`, `plugin`, etc. +3. **Parameters** — minimum form fields (name, owner, repo URL, …) +4. **Steps** — fetch skeleton only, or publish + register too? +5. **SCM** — GitHub, GitLab, Bitbucket? + +## Step 1b: Match reference examples (recommended) + +Before writing files, query the curated catalog and suggest 1–3 study references: + +```bash +python /scripts/list_examples.py \ + --match "