Compose Templates
How to combine multiple templates into one
How to Compose Templates
Compose multiple templates to create powerful combinations while maintaining separation of concerns.
What is Composition?
Template composition allows one template to include and use other templates:
cyan.yaml
templates:- my-org/base-setup:1- my-org/docker-config:2
Composition Patterns
Layered Architecture
Build templates in layers:
- Base template - Core project structure
- Feature templates - Optional features (Docker, CI/CD)
- Platform templates - Deployment targets
cyan.yaml
# Full-stack web app templatetemplates:- shared/node-base:1 # Node.js project structure- shared/typescript:2 # TypeScript configuration- shared/eslint:1 # Linting setup- frontend/react:3 # React frontend- backend/express:2 # Express backend- deploy/docker:1 # Docker configuration
Feature Composition
Let users compose features they need by specifying which templates to include:
cyan.yaml
# user selects featurestemplates:- base/project:1- features/typescript:2- features/docker:1- features/ci-github:3
Key Considerations
Namespaced Keys
When composing templates, key collisions cause unexpected behavior. Always namespace your keys.
template.ts
// Template Aconst name = await i.text('Name?', 'template-a.project.name', '...');// Template B (composed)const name = await i.text('Name?', 'template-b.project.name', '...');// Both get their own answers
Intentional Key Sharing
Share values intentionally via common key names:
template.ts
// Both templates use the same project name// Template Aconst projectName = await i.text('Project name?', 'global.project.name', '...');// Template B (composed) - gets the same answerconst projectName = await i.text('Project name?', 'global.project.name', '...');
File Conflicts
Handle file conflicts in composition:
- Override - Later templates override earlier ones
- Merge - For configs, merge content
- Fail - Error on conflict (requires handling)
Composition in Code
Access composed templates in your logic:
template.ts
import { StartTemplateWithLambda, GlobType } from '@atomicloud/cyan-sdk';StartTemplateWithLambda(async (i, d) => {// Get answer from a composed templateconst baseName = await i.text('Project name from base?','base-template.project.name','...');// Add your own filesreturn {processors: [{name: 'cyan/default',files: [{ root: 'templates', glob: '**/*', exclude: [], type: GlobType.Template }],config: {vars: {name: baseName // Use value from base template}}}]};});
Best Practices
- Single responsibility - Each template does one thing well
- Namespace keys - Avoid accidental collisions
- Document dependencies - List required composed templates
- Version pin - Specify exact versions in composition
Example: Microservice Template
cyan.yaml
# cyan.yaml for microservice templatename: company/microserviceversion: 1templates:- company/node-base:2- company/typescript:1- company/docker:3- company/grpc:1- company/observability:2
Related
- Use Keys - Keys and namespacing
- 3-Way Merge - Update mechanics
- Template Registry - Publishing templates