mirror of
https://github.com/ChristianLempa/boilerplates.git
synced 2026-08-24 10:04:44 -05:00
docs: reorganize contributor and agent guidance
This commit is contained in:
@@ -1,725 +1,63 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
Guidance for AI Agents working with this repository.
|
Guidance for AI coding agents working in this repository.
|
||||||
|
|
||||||
## Project Overview
|
## Project
|
||||||
|
|
||||||
A Python CLI for managing infrastructure boilerplates, plus archetypes and template-loading logic. Built with Typer (CLI) and Jinja2 (templating).
|
Project name: Boilerplates CLI
|
||||||
|
|
||||||
**Important library location note:**
|
A Python CLI for managing infrastructure boilerplates, template libraries, template rendering, and validation. Built with Typer and Jinja2.
|
||||||
- The canonical template library does **not** live in this repository for modern development.
|
|
||||||
- The checked-in `library/` directory here is **legacy/backward-compatibility content for versions older than `0.2.0`**.
|
|
||||||
- For `0.2.0+`, the active template library lives in the separate GitHub repository `boilerplates-library`.
|
|
||||||
|
|
||||||
## Development Setup
|
## Scope and precedence
|
||||||
|
|
||||||
### Running and Testing
|
- This file applies to the repository tree rooted at the directory containing this `AGENTS.md` file.
|
||||||
|
- More deeply nested `AGENTS.md` files may add or override instructions for their subtrees.
|
||||||
|
- Before GitHub-facing work such as issues, branches, PRs, merges, tags, or releases, read this file and the relevant docs listed below.
|
||||||
|
- For release-related work, always read `RELEASE.md` first.
|
||||||
|
|
||||||
```bash
|
## Repository map
|
||||||
# Create and activate a local virtual environment first
|
|
||||||
python3 -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
python3 -m pip install --upgrade pip
|
|
||||||
python3 -m pip install -e ".[test]"
|
|
||||||
|
|
||||||
# Run the CLI application
|
- Source: `cli/` - main CLI application and core runtime code.
|
||||||
python3 -m cli
|
- Tests: `tests/` - automated tests, fixtures, and test utilities.
|
||||||
# Debugging and Testing commands
|
- Contribution guide: `CONTRIBUTING.md` - setup, validation, GitHub workflow, and command overview.
|
||||||
python3 -m cli --log-level DEBUG compose list
|
- Docs: `docs/` - developer documentation and architecture notes.
|
||||||
# Run tests
|
- Release process: `RELEASE.md` - release PR, version, changelog, tag, and publish workflow.
|
||||||
python3 -m pytest
|
- Scripts: `scripts/` - installer and helper scripts.
|
||||||
```
|
- GitHub automation: `.github/` - workflows, issue/PR templates, and repository scripts.
|
||||||
|
- Legacy library: `library/` - backward-compatibility template content for versions older than `0.2.0`; not the canonical modern template library.
|
||||||
|
|
||||||
### Production-Ready Testing
|
## Documentation
|
||||||
|
|
||||||
For detailed information about testing boilerplates in a production-like environment before release, see **WARP-LOCAL.md** (local file, not in git). This document covers:
|
Project documentation lives in `docs/`.
|
||||||
- Test server infrastructure and Docker contexts
|
|
||||||
- Step-by-step testing procedures for Docker Compose, Swarm, and Kubernetes
|
|
||||||
- Comprehensive testing checklists
|
|
||||||
- Production release criteria
|
|
||||||
|
|
||||||
### Linting and Formatting
|
Agents should read the relevant docs before making changes and update docs when behavior, configuration, or workflows change.
|
||||||
|
|
||||||
Should **always** happen before pushing anything to the repository and before making a release.
|
### Documentation map
|
||||||
|
|
||||||
- Use `ruff` for Python code:
|
- `docs/architecture.md` - repository layout, module system, and core runtime components
|
||||||
- `ruff check --fix .` - Check and auto-fix linting errors (including unused imports)
|
- `docs/templates.md` - template format, variables, rendering, and validation behavior
|
||||||
- `ruff format .` - Format code according to style guidelines
|
- `docs/libraries.md` - library configuration and canonical external template library guidance
|
||||||
- Both commands must be run before committing
|
- `docs/display.md` - DisplayManager and CLI output rules
|
||||||
- Minimum pre-release verification:
|
|
||||||
- `ruff check --fix .`
|
|
||||||
- `ruff format .`
|
|
||||||
- `python3 -m pytest`
|
|
||||||
- Agents should treat these as release gates, not optional cleanup.
|
|
||||||
|
|
||||||
### Project Management and Git
|
|
||||||
|
|
||||||
The project is stored in a public GitHub Repository, use issues, and branches for features/bugfixes and open PRs for merging.
|
|
||||||
|
|
||||||
**Naming Conventions and Best-Practices:**
|
|
||||||
- Branches, PRs: `feature/2314-add-feature`, `problem/1249-fix-bug`
|
|
||||||
- Issues should have clear titles and descriptions, link related issues/PRs, and have appropriate labels like (problem, feature, discussion, question).
|
|
||||||
- Commit messages should be clear and concise, following the format: `type(scope): subject` (e.g., `fix(compose): correct variable parsing`).
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### File Structure
|
|
||||||
|
|
||||||
- `cli/` - Python CLI application source code
|
|
||||||
- `cli/core/` - Core Components of the CLI application
|
|
||||||
- `cli/modules/` - Modules implementing technology-specific functions
|
|
||||||
- `cli/__main__.py` - CLI entry point, auto-discovers modules and registers commands
|
|
||||||
- `library/` - Legacy in-repo template content kept only for backward compatibility with versions older than `0.2.0`
|
|
||||||
- Do not treat this directory as the canonical source for new template work
|
|
||||||
- The active `0.2.0+` library is maintained in the separate GitHub repository `boilerplates-library`
|
|
||||||
- `archetypes/` - Testing tool for template snippets (archetype development)
|
|
||||||
- `archetypes/__init__.py` - Package initialization
|
|
||||||
- `archetypes/__main__.py` - CLI tool entry point
|
|
||||||
- `archetypes/<module>/` - Module-specific archetype snippets (e.g., `archetypes/compose/`)
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
|
|
||||||
- `cli/core/collection.py` - VariableCollection class (manages sections and variables)
|
|
||||||
- **Key Attributes**: `_sections` (dict of VariableSection objects), `_variable_map` (flat lookup dict)
|
|
||||||
- **Key Methods**: `get_satisfied_values()` (returns enabled variables), `apply_defaults()`, `sort_sections()`
|
|
||||||
- `cli/core/config.py` - Configuration management (loading, saving, validation)
|
|
||||||
- `cli/core/display/` - Centralized CLI output rendering package (**Always use DisplayManager - never print directly**)
|
|
||||||
- `__init__.py` - Package exports (DisplayManager, DisplaySettings, IconManager)
|
|
||||||
- `display_manager.py` - Main DisplayManager facade
|
|
||||||
- `display_settings.py` - DisplaySettings configuration class
|
|
||||||
- `icon_manager.py` - IconManager for Nerd Font icons
|
|
||||||
- `variable_display.py` - VariableDisplayManager for variable rendering
|
|
||||||
- `template_display.py` - TemplateDisplayManager for template display
|
|
||||||
- `status_display.py` - StatusDisplayManager for status messages
|
|
||||||
- `table_display.py` - TableDisplayManager for table rendering
|
|
||||||
- `cli/core/exceptions.py` - Custom exceptions for error handling (**Always use this for raising errors**)
|
|
||||||
- `cli/core/library.py` - LibraryManager for template discovery from git-synced libraries and static file paths
|
|
||||||
- `cli/core/module.py` - Abstract base class for modules (defines standard commands)
|
|
||||||
- `cli/core/prompt.py` - Interactive CLI prompts using rich library
|
|
||||||
- `cli/core/registry.py` - Central registry for module classes (auto-discovers modules)
|
|
||||||
- `cli/core/repo.py` - Repository management for syncing git-based template libraries
|
|
||||||
- `cli/core/section.py` - VariableSection class (stores section metadata and variables)
|
|
||||||
- **Key Attributes**: `key`, `title`, `toggle`, `needs`, `variables` (dict of Variable objects)
|
|
||||||
- `cli/core/template.py` - Template Class for parsing, managing and rendering templates
|
|
||||||
- `cli/core/variable.py` - Variable class (stores variable metadata and values)
|
|
||||||
- **Key Attributes**: `name`, `type`, `value` (stores default or current value), `description`, `sensitive`, `needs`
|
|
||||||
- **Note**: Default values are stored in `value` attribute, NOT in a separate `default` attribute
|
|
||||||
- `cli/core/validators.py` - Semantic validators for template content (Docker Compose, YAML, etc.)
|
|
||||||
- `cli/core/version.py` - Version comparison utilities for semantic versioning
|
|
||||||
|
|
||||||
### Modules
|
|
||||||
|
|
||||||
**Currently supported kinds in code:**
|
|
||||||
- `ansible`
|
|
||||||
- `compose`
|
|
||||||
- `helm`
|
|
||||||
- `kubernetes`
|
|
||||||
- `packer`
|
|
||||||
- `static`
|
|
||||||
- `swarm`
|
|
||||||
- `terraform`
|
|
||||||
|
|
||||||
These are the kinds supported by the registered CLI modules in `cli/modules/`. When answering "what kinds do we support?", prefer this code-level list over whatever legacy content happens to exist under `library/`.
|
|
||||||
|
|
||||||
**Module Structure:**
|
|
||||||
Modules can be either single files or packages:
|
|
||||||
- **Single file**: `cli/modules/modulename.py` (for simple modules)
|
|
||||||
|
|
||||||
**Creating Modules:**
|
|
||||||
- Subclass `Module` from `cli/core/module.py`
|
|
||||||
- Call `registry.register(YourModule)` at module bottom
|
|
||||||
- Auto-discovered and registered at CLI startup
|
|
||||||
|
|
||||||
**Module Discovery and Registration:**
|
|
||||||
|
|
||||||
The system automatically discovers and registers modules at startup:
|
|
||||||
|
|
||||||
1. **Discovery**: CLI `__main__.py` imports all Python files in `cli/modules/` directory
|
|
||||||
2. **Registration**: Each module file calls `registry.register(ModuleClass)` at module level
|
|
||||||
3. **Storage**: Registry stores module classes in a central dictionary by module name
|
|
||||||
4. **Command Generation**: CLI framework auto-generates subcommands for each registered module
|
|
||||||
5. **Instantiation**: Modules are instantiated on-demand when commands are invoked
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- No manual registration needed - just add a file to `cli/modules/`
|
|
||||||
- Modules are self-contained - can be added/removed without modifying core code
|
|
||||||
- Type-safe - registry validates module interfaces at registration time
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
compose/
|
|
||||||
v1.0.json
|
|
||||||
v1.1.json
|
|
||||||
v1.2.json
|
|
||||||
terraform/
|
|
||||||
v1.0.json
|
|
||||||
ansible/
|
|
||||||
v1.0.json
|
|
||||||
...other modules...
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"key": "section_key",
|
|
||||||
"title": "Section Title",
|
|
||||||
"description": "Optional section description",
|
|
||||||
"toggle": "optional_toggle_variable_name",
|
|
||||||
"needs": "optional_dependency",
|
|
||||||
"required": true,
|
|
||||||
"vars": [
|
|
||||||
{
|
|
||||||
"name": "variable_name",
|
|
||||||
"type": "str",
|
|
||||||
"description": "Variable description",
|
|
||||||
"default": "default_value",
|
|
||||||
"required": true,
|
|
||||||
"sensitive": false,
|
|
||||||
"autogenerated": false,
|
|
||||||
"options": ["option1", "option2"],
|
|
||||||
"needs": "other_var=value",
|
|
||||||
"extra": "Additional help text"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
class MyModule(Module):
|
|
||||||
name = "mymodule"
|
|
||||||
|
|
||||||
# Convert JSON array to OrderedDict format
|
|
||||||
return self._convert_json_to_dict(json_spec)
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Auto-created toggle variables**: Sections with `toggle` automatically create boolean variables
|
|
||||||
- **Conditional visibility**: Variables use `needs` constraints to show/hide based on other variable values
|
|
||||||
- **Mode-based organization**: Related settings grouped by operational mode (e.g., network_mode, volume_mode)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Older Python-based `spec_v*.py` files have been migrated to JSON. The module `__init__.py` now:
|
|
||||||
2. Converts JSON array format to OrderedDict for backward compatibility
|
|
||||||
|
|
||||||
**Existing Modules:**
|
|
||||||
- Other modules (ansible, terraform, kubernetes, helm, packer) - Work in Progress
|
|
||||||
|
|
||||||
**(Work in Progress):** terraform, docker, ansible, kubernetes, packer modules
|
|
||||||
|
|
||||||
### LibraryManager
|
|
||||||
|
|
||||||
- Loads libraries from config file
|
|
||||||
- Stores Git Libraries under: `~/.config/boilerplates/libraries/{name}/`
|
|
||||||
- Uses sparse-checkout to clone only template directories for git-based libraries (avoiding unnecessary files)
|
|
||||||
- Supports two library types: **git** (synced from repos) and **static** (local directories)
|
|
||||||
- Priority determined by config order (first = highest)
|
|
||||||
- In this repository, the checked-in `library/` directory is legacy compatibility data only and should not be treated as the primary source for current template development
|
|
||||||
- For current `0.2.0+` template work, use the external GitHub repository `boilerplates-library`
|
|
||||||
|
|
||||||
**Library Types:**
|
|
||||||
- `git`: Requires `url`, `branch`, `directory` fields
|
|
||||||
- `static`: Requires `path` field (absolute or relative to config)
|
|
||||||
|
|
||||||
**Duplicate Handling:**
|
|
||||||
- Within same library: Raises `DuplicateTemplateError`
|
|
||||||
- Across libraries: Uses qualified IDs (e.g., `alloy.default`, `alloy.local`)
|
|
||||||
- Simple IDs use priority: `compose show alloy` loads from first library
|
|
||||||
- Qualified IDs target specific library: `compose show alloy.local`
|
|
||||||
|
|
||||||
**Config Example:**
|
|
||||||
```yaml
|
|
||||||
libraries:
|
|
||||||
- name: default # Highest priority (checked first)
|
|
||||||
type: git
|
|
||||||
url: https://github.com/user/templates.git
|
|
||||||
branch: main
|
|
||||||
directory: library
|
|
||||||
- name: local # Lower priority
|
|
||||||
type: static
|
|
||||||
path: ~/my-templates
|
|
||||||
url: '' # Backward compatibility fields
|
|
||||||
branch: main
|
|
||||||
directory: .
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Static libraries include dummy `url`/`branch`/`directory` fields for backward compatibility with older CLI versions.
|
|
||||||
|
|
||||||
### ConfigManager
|
|
||||||
|
|
||||||
- User Config stored in `~/.config/boilerplates/config.yaml`
|
|
||||||
|
|
||||||
### DisplayManager and IconManager
|
|
||||||
|
|
||||||
**CRITICAL RULE - NEVER violate this:**
|
|
||||||
- NEVER use `console.print()` outside of display manager classes (`cli/core/display/` directory)
|
|
||||||
- NEVER import `Console` from `rich.console` except in display manager classes or `cli/__main__.py`
|
|
||||||
- ALWAYS use `module_instance.display.display_*()` or `display.display_*()` methods for ALL output
|
|
||||||
- Display managers (`cli/core/display/*.py`) are the ONLY exception - they implement console output
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- `DisplayManager` provides a **centralized interface** for ALL CLI output rendering
|
|
||||||
- Direct console usage bypasses formatting standards, icon management, and output consistency
|
|
||||||
- `IconManager` provides **Nerd Font icons** internally for DisplayManager - never use emojis or direct icons
|
|
||||||
|
|
||||||
**DisplayManager Architecture** (Refactored for Single Responsibility Principle):
|
|
||||||
|
|
||||||
`DisplayManager` acts as a facade that delegates to specialized manager classes:
|
|
||||||
|
|
||||||
1. **VariableDisplayManager** - Handles all variable-related rendering
|
|
||||||
- `render_variable_value()` - Variable value formatting with context awareness
|
|
||||||
- `render_section()` - Section header display
|
|
||||||
- `render_variables_table()` - Complete variables table with dependencies
|
|
||||||
|
|
||||||
2. **TemplateDisplayManager** - Handles all template-related rendering
|
|
||||||
- `render_template()` - Main template display coordinator
|
|
||||||
- `render_template_header()` - Template metadata display
|
|
||||||
- `render_file_tree()` - Template file structure visualization
|
|
||||||
- `render_file_generation_confirmation()` - Files preview before generation
|
|
||||||
|
|
||||||
3. **StatusDisplayManager** - Handles status messages and error display
|
|
||||||
- `display_message()` - Core message formatting with level-based routing
|
|
||||||
- `display_error()`, `display_warning()`, `display_success()`, `display_info()` - Convenience methods
|
|
||||||
- `display_template_render_error()` - Detailed render error display
|
|
||||||
- `display_warning_with_confirmation()` - Interactive warning prompts
|
|
||||||
|
|
||||||
4. **TableDisplayManager** - Handles table rendering
|
|
||||||
- `render_templates_table()` - Templates list with library indicators
|
|
||||||
- `render_status_table()` - Status tables with success/error indicators
|
|
||||||
- `render_config_tree()` - Configuration tree visualization
|
|
||||||
|
|
||||||
**Usage Pattern:**
|
|
||||||
```python
|
|
||||||
# External code uses DisplayManager methods (backward compatible)
|
|
||||||
display = DisplayManager()
|
|
||||||
display.display_template(template, template_id)
|
|
||||||
|
|
||||||
# Internally, DisplayManager delegates to specialized managers
|
|
||||||
# display.templates.render_template(template, template_id)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Design Principles:**
|
|
||||||
- External code calls `DisplayManager` methods only
|
|
||||||
- `DisplayManager` delegates to specialized managers internally
|
|
||||||
- Each specialized manager has a single, focused responsibility
|
|
||||||
- Backward compatibility maintained through delegation methods
|
|
||||||
- All managers can access parent DisplayManager via `self.parent`
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
|
|
||||||
Templates are directory-based. Each template is a directory containing all the necessary files and subdirectories for the boilerplate.
|
|
||||||
|
|
||||||
### Template Rendering Flow
|
|
||||||
|
|
||||||
**How templates are loaded and rendered:**
|
|
||||||
|
|
||||||
1. **Discovery**: LibraryManager finds template directories containing `template.json`
|
|
||||||
2. **Parsing**: Template class loads and parses the template metadata and spec
|
|
||||||
5. **Variable Merging**: Template spec overrides are merged with module spec (precedence: module < template < user config < CLI)
|
|
||||||
6. **Collection Building**: VariableCollection is constructed with merged variables and sections
|
|
||||||
7. **Dependency Resolution**: Sections are topologically sorted based on `needs` constraints
|
|
||||||
8. **Variable Resolution**: Variables with `needs` constraints are evaluated for visibility
|
|
||||||
9. **Jinja2 Rendering**: Template files (`.j2`) are rendered with final variable values
|
|
||||||
10. **Sanitization**: Rendered output is cleaned (whitespace, blank lines, trailing newline)
|
|
||||||
11. **Validation**: Optional semantic validation (YAML structure, Docker Compose config, etc.)
|
|
||||||
|
|
||||||
**Key Architecture Points:**
|
|
||||||
- Variable visibility is dynamic based on `needs` constraints (evaluated at prompt/render time)
|
|
||||||
- Jinja2 templates support `{% include %}` and `{% import %}` for composition
|
|
||||||
- Template loading can read configured libraries from disk, but the checked-in `library/` tree in this repo is legacy compatibility data, not the canonical `0.2.0+` library source
|
|
||||||
|
|
||||||
### Template Structure
|
|
||||||
|
|
||||||
Requires `template.json` with metadata and variables:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"slug": "my-service",
|
|
||||||
"kind": "compose",
|
|
||||||
"metadata": {
|
|
||||||
"name": "My Service Template",
|
|
||||||
"description": "A template for a service.",
|
|
||||||
"author": "Your Name",
|
|
||||||
"date": "2024-01-01",
|
|
||||||
"version": {
|
|
||||||
"name": "v1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"variables": [
|
|
||||||
{
|
|
||||||
"name": "general",
|
|
||||||
"title": "General",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"name": "service_name",
|
|
||||||
"type": "str",
|
|
||||||
"title": "Service name"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Metadata Versioning
|
|
||||||
|
|
||||||
**Template Version Field:**
|
|
||||||
The `metadata.version` field in `template.json` is a structured object and may be partial:
|
|
||||||
- `name`
|
|
||||||
- `source_dep_name`
|
|
||||||
- `source_dep_version`
|
|
||||||
- `source_dep_digest`
|
|
||||||
- `upstream_ref`
|
|
||||||
- `notes`
|
|
||||||
|
|
||||||
**Rationale:** This separates the user-facing template version label from the tracked upstream dependency metadata used by the template snapshot.
|
|
||||||
|
|
||||||
**Application Version Variables:**
|
|
||||||
- **IMPORTANT**: Application/image versions should be **hardcoded** in template files (e.g., `image: nginx:1.25.3`)
|
|
||||||
- Do NOT create template variables for application versions (e.g., no `nginx_version` variable)
|
|
||||||
- Users should update the template file directly when they need a different version
|
|
||||||
- This prevents version mismatches and ensures templates are tested with specific, known versions
|
|
||||||
- Exception: Only create version variables if there's a strong technical reason (e.g., multi-component version pinning)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"slug": "my-template",
|
|
||||||
"kind": "compose",
|
|
||||||
"metadata": {
|
|
||||||
"name": "My Template",
|
|
||||||
"version": {
|
|
||||||
"name": "v1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"variables": [
|
|
||||||
{
|
|
||||||
"name": "general",
|
|
||||||
"title": "General",
|
|
||||||
"items": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**How It Works:**
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- Older templates work with newer module versions (backward compatibility)
|
|
||||||
- Version comparison uses MAJOR.MINOR format (e.g., "1.0" < "1.2" < "2.0")
|
|
||||||
|
|
||||||
**When to Use:**
|
|
||||||
|
|
||||||
**Single-File Module Example:**
|
|
||||||
```python
|
|
||||||
class SimpleModule(Module):
|
|
||||||
name = "simple"
|
|
||||||
description = "Simple module"
|
|
||||||
spec = VariableCollection.from_dict({...}) # Single spec
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
# cli/modules/modulename/__init__.py
|
|
||||||
class ExampleModule(Module):
|
|
||||||
name = "modulename"
|
|
||||||
description = "Module description"
|
|
||||||
|
|
||||||
# Dynamically load the appropriate spec version
|
|
||||||
spec_module = importlib.import_module(f".{version_file}", package=__package__)
|
|
||||||
return spec_module.get_spec()
|
|
||||||
```
|
|
||||||
|
|
||||||
**Version Management:**
|
|
||||||
- CLI version is defined in `cli/__init__.py` as `__version__`
|
|
||||||
- pyproject.toml version must match `__version__` for releases
|
|
||||||
- GitHub release workflow validates version consistency
|
|
||||||
|
|
||||||
### Template Files
|
|
||||||
|
|
||||||
- **Jinja2 Templates (`.j2`)**: Rendered by Jinja2, `.j2` extension removed in output. Support `{% include %}` and `{% import %}`.
|
|
||||||
- **Static Files**: Non-`.j2` files copied as-is.
|
|
||||||
- **Sanitization**: Auto-sanitized (single blank lines, no leading blanks, trimmed whitespace, single trailing newline).
|
|
||||||
- **Shortcodes**: Template descriptions support emoji-style shortcodes (e.g., `:warning:`, `:info:`, `:docker:`) which are automatically replaced with Nerd Font icons during display. Add new shortcodes to `IconManager.SHORTCODES` dict.
|
|
||||||
|
|
||||||
### Docker Compose Best Practices
|
|
||||||
|
|
||||||
**Traefik Integration:**
|
|
||||||
|
|
||||||
When using Traefik with Docker Compose, the `traefik.docker.network` label is **CRITICAL** for stacks with multiple networks. When containers are connected to multiple networks, Traefik must know which network to use for routing.
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- Review `archetypes/compose/` directory for reference implementations of Traefik integration patterns
|
|
||||||
- The `traefik.docker.network={{ traefik_network }}` label must be present in both standard `labels:` and `deploy.labels:` sections
|
|
||||||
- Standard mode and Swarm mode require different label configurations - check archetypes for examples
|
|
||||||
|
|
||||||
### Variables
|
|
||||||
|
|
||||||
**How Templates Inherit Variables:**
|
|
||||||
|
|
||||||
|
|
||||||
**When to Define Template Variables:**
|
|
||||||
|
|
||||||
You only need to define variables in your template's `spec` section when:
|
|
||||||
1. **Overriding defaults**: Change default values for module variables (e.g., hardcode `service_name` for your specific app)
|
|
||||||
|
|
||||||
**Variable Precedence** (lowest to highest):
|
|
||||||
1. Module `spec` (defaults for all templates of that kind)
|
|
||||||
2. Template `spec` (overrides module defaults)
|
|
||||||
3. User `config.yaml` (overrides template and module defaults)
|
|
||||||
4. CLI `--var` (highest priority)
|
|
||||||
|
|
||||||
**Template Variable Override Rules:**
|
|
||||||
- **Override module defaults**: Only specify properties that differ from module spec (e.g., change `default` value)
|
|
||||||
- **Create new variables**: Define template-specific variables not in module spec
|
|
||||||
- **Minimize duplication**: Do NOT re-specify `type`, `description`, or other properties if they remain unchanged from module spec
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```yaml
|
|
||||||
# Template spec ONLY needs to override specific defaults:
|
|
||||||
spec:
|
|
||||||
general:
|
|
||||||
vars:
|
|
||||||
service_name:
|
|
||||||
default: whoami # Only override the default, type already defined in module
|
|
||||||
```
|
|
||||||
|
|
||||||
**Variable Types:**
|
|
||||||
- `str` (default), `int`, `float`, `bool`
|
|
||||||
- `email` - Email validation with regex
|
|
||||||
- `url` - URL validation (requires scheme and host)
|
|
||||||
- `hostname` - Hostname/domain validation
|
|
||||||
- `enum` - Choice from `options` list
|
|
||||||
|
|
||||||
**Variable Properties:**
|
|
||||||
- `sensitive: true` - Masked in prompts/display (e.g., passwords)
|
|
||||||
- `autogenerated: true` - Auto-generates value if empty (shows `*auto` placeholder)
|
|
||||||
- `default` - Default value
|
|
||||||
- `description` - Variable description
|
|
||||||
- `prompt` - Custom prompt text (overrides description)
|
|
||||||
- `extra` - Additional help text
|
|
||||||
- `options` - List of valid values (for enum type)
|
|
||||||
|
|
||||||
**Section Features:**
|
|
||||||
- **Toggle Settings**: Conditional sections via `toggle: "bool_var_name"`. If false, section is skipped.
|
|
||||||
- **IMPORTANT**: When a section has `toggle: "var_name"`, that boolean variable is AUTO-CREATED by the system
|
|
||||||
- Example: `ports` section with `toggle: "ports_enabled"` automatically provides `ports_enabled` boolean
|
|
||||||
- **Dependencies**: Use `needs: "section_name"` or `needs: ["sec1", "sec2"]`. Dependent sections only shown when dependencies are enabled.
|
|
||||||
|
|
||||||
**Dependency Resolution Architecture:**
|
|
||||||
|
|
||||||
Sections and variables support `needs` constraints to control visibility based on other variables.
|
|
||||||
|
|
||||||
**Section-Level Dependencies:**
|
|
||||||
- Format: `needs: "section_name"` or `needs: ["sec1", "sec2"]`
|
|
||||||
- Section only appears when all required sections are enabled (their toggle variables are true)
|
|
||||||
- Automatically validated: detects circular, missing, and self-dependencies
|
|
||||||
- Topologically sorted: ensures dependencies are prompted/processed before dependents
|
|
||||||
|
|
||||||
**Variable-Level Dependencies:**
|
|
||||||
- Format: `needs: "var_name=value"` or `needs: "var1=val1;var2=val2"` (semicolon-separated)
|
|
||||||
- Variable only visible when constraint is satisfied (e.g., `needs: "network_mode=bridge"`)
|
|
||||||
- Supports multiple values: `needs: "network_mode=bridge,macvlan"` (comma = OR)
|
|
||||||
- Evaluated dynamically at prompt and render time
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- Circular dependencies: Raises error if A needs B and B needs A
|
|
||||||
- Missing dependencies: Raises error if referencing non-existent sections/variables
|
|
||||||
- Self-dependencies: Raises error if section depends on itself
|
|
||||||
|
|
||||||
**Example Section with Dependencies:**
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
spec:
|
|
||||||
traefik:
|
|
||||||
title: Traefik
|
|
||||||
required: false
|
|
||||||
toggle: traefik_enabled
|
|
||||||
vars:
|
|
||||||
traefik_enabled:
|
|
||||||
type: bool
|
|
||||||
default: false
|
|
||||||
traefik_host:
|
|
||||||
type: hostname
|
|
||||||
|
|
||||||
traefik_tls:
|
|
||||||
title: Traefik TLS/SSL
|
|
||||||
needs: traefik
|
|
||||||
toggle: traefik_tls_enabled
|
|
||||||
vars:
|
|
||||||
traefik_tls_enabled:
|
|
||||||
type: bool
|
|
||||||
default: true
|
|
||||||
traefik_tls_certresolver:
|
|
||||||
type: str
|
|
||||||
sensitive: false
|
|
||||||
default: myresolver
|
|
||||||
```
|
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
**Jinja2 Validation:**
|
Default checks before finishing code changes:
|
||||||
- Templates validated for Jinja2 syntax errors during load
|
|
||||||
- Checks for undefined variables (variables used but not declared in spec)
|
|
||||||
- Built into Template class
|
|
||||||
|
|
||||||
**Semantic Validation:**
|
|
||||||
- Validator registry system in `cli/core/validators.py`
|
|
||||||
- Extensible: `ContentValidator` abstract base class
|
|
||||||
- Built-in validators: `DockerComposeValidator`, `YAMLValidator`
|
|
||||||
- Validates rendered output (YAML structure, Docker Compose config, etc.)
|
|
||||||
- Triggered via `compose validate` command with `--semantic` flag (enabled by default)
|
|
||||||
|
|
||||||
## Prompt
|
|
||||||
|
|
||||||
Uses `rich` library for interactive prompts. Supports:
|
|
||||||
- Text input
|
|
||||||
- Password input (masked, for `sensitive: true` variables)
|
|
||||||
- Selection from list (single/multiple)
|
|
||||||
- Confirmation (yes/no)
|
|
||||||
- Default values
|
|
||||||
- Autogenerated variables (show `*auto` placeholder, generate on render)
|
|
||||||
|
|
||||||
To skip the prompt use the `--no-interactive` flag, which will use defaults or empty values.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
**Standard Module Commands** (auto-registered for all modules):
|
|
||||||
- `list` - List all templates
|
|
||||||
- `search <query>` - Search templates by ID
|
|
||||||
- `show <id>` - Show template details
|
|
||||||
- `generate <id> -o <directory>` - Generate from template (supports `--dry-run`, `--var`, `--no-interactive`)
|
|
||||||
- `validate [template_id]` - Validate template(s) (Jinja2 + semantic). Omit template_id to validate all templates
|
|
||||||
- `defaults` - Manage config defaults (`get`, `set`, `rm`, `clear`, `list`)
|
|
||||||
|
|
||||||
**Core Commands:**
|
|
||||||
- `repo sync` - Sync git-based libraries
|
|
||||||
- `repo list` - List configured libraries
|
|
||||||
|
|
||||||
## Archetypes
|
|
||||||
|
|
||||||
The `archetypes` package provides reusable, standardized template building blocks for creating boilerplates. Archetypes are modular Jinja2 snippets that represent specific configuration sections.
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
|
|
||||||
1. **Template Development**: Provide standardized, tested building blocks for creating new templates
|
|
||||||
2. **Testing & Validation**: Enable testing of specific configuration sections in isolation with different variable combinations
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List available archetypes for a module
|
ruff check --fix .
|
||||||
python3 -m archetypes compose list
|
ruff format .
|
||||||
|
python3 -m pytest
|
||||||
# Preview an archetype component
|
|
||||||
python3 -m archetypes compose generate <archetype-name>
|
|
||||||
|
|
||||||
# Test with variable overrides
|
|
||||||
python3 -m archetypes compose generate <archetype-name> \
|
|
||||||
--var traefik_enabled=true \
|
|
||||||
--var swarm_enabled=true
|
|
||||||
|
|
||||||
# Validate templates against archetypes
|
|
||||||
python3 -m archetypes compose validate # All templates
|
|
||||||
python3 -m archetypes compose validate <template> # Single template
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Archetype Validation
|
See `CONTRIBUTING.md` for setup and workflow details.
|
||||||
|
|
||||||
The `validate` command compares templates against archetypes to measure coverage and identify which archetype patterns are being used.
|
## Project conventions
|
||||||
|
|
||||||
**What it does:**
|
- Follow existing project structure, naming, and patterns before introducing new ones.
|
||||||
- Compares each template file against all available archetypes using **structural pattern matching**
|
- Keep changes focused on the requested task; avoid unrelated refactors or broad rewrites.
|
||||||
- Abstracts away specific values to focus on:
|
- When adding or changing behavior, update relevant docs and tests where practical.
|
||||||
- **Jinja2 control flow**: `{% if %}`, `{% elif %}`, `{% else %}`, `{% for %}` structures
|
- Do not commit secrets, tokens, private keys, or environment-specific credentials.
|
||||||
- **YAML structure**: Key names, indentation, and nesting patterns
|
- Use custom exceptions from `cli/core/exceptions.py` for user-facing errors where applicable.
|
||||||
- **Variable usage patterns**: Presence of `{{ }}` placeholders (not specific names)
|
- Use `DisplayManager` for user-facing command output; never print directly from command, module, config, repository, validation, or template-rendering code. See `docs/display.md`.
|
||||||
- **Wildcard placeholders**: `__ANY__`, `__ANYSTR__`, `__ANYINT__`, `__ANYBOOL__`
|
- Do not treat `library/` as the canonical source for current template work. For `0.2.0+`, active templates live in the separate `boilerplates-library` repository. See `docs/libraries.md`.
|
||||||
- **Repeat markers**: `{# @repeat-start #}` / `{# @repeat-end #}`
|
- When answering which template kinds are supported, prefer registered modules in `cli/modules/` over legacy library contents.
|
||||||
- **Optional markers**: `{# @optional-start #}` / `{# @optional-end #}`
|
|
||||||
- This allows detection of archetypes even when specific values differ (e.g., `grafana_data` vs `alloy_data`)
|
|
||||||
- Calculates **containment ratio**: what percentage of each archetype structure is found within the template
|
|
||||||
- Reports usage status: **exact** (≥95%), **high** (≥70%), **partial** (≥30%), or **none** (<30%)
|
|
||||||
- Provides coverage metrics: (exact + high matches) / total archetypes
|
|
||||||
|
|
||||||
### Advanced Pattern Matching in Archetypes
|
|
||||||
|
|
||||||
Archetypes support special annotations for flexible pattern matching:
|
|
||||||
|
|
||||||
**Wildcard Placeholders** (match any value):
|
|
||||||
- `__ANY__` - Matches anything
|
|
||||||
- `__ANYSTR__` - Matches any string
|
|
||||||
- `__ANYINT__` - Matches any integer
|
|
||||||
- `__ANYBOOL__` - Matches any boolean
|
|
||||||
|
|
||||||
**Repeat Markers** (pattern can appear 1+ times):
|
|
||||||
```yaml
|
|
||||||
{# @repeat-start #}
|
|
||||||
pattern
|
|
||||||
{# @repeat-end #}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Optional Markers** (section may or may not exist):
|
|
||||||
```yaml
|
|
||||||
{# @optional-start #}
|
|
||||||
pattern
|
|
||||||
{# @optional-end #}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```yaml
|
|
||||||
volumes:
|
|
||||||
{# @repeat-start #}
|
|
||||||
__ANY__:
|
|
||||||
driver: local
|
|
||||||
{# @repeat-end #}
|
|
||||||
```
|
|
||||||
Matches any number of volumes with `driver: local`
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
```bash
|
|
||||||
# Validate all templates in library - shows summary table
|
|
||||||
python3 -m archetypes compose validate
|
|
||||||
|
|
||||||
# Validate specific template - shows detailed archetype breakdown
|
|
||||||
python3 -m archetypes compose validate whoami
|
|
||||||
|
|
||||||
# Validate templates in custom location
|
|
||||||
python3 -m archetypes compose validate --library /path/to/templates
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output:**
|
|
||||||
- **Summary mode** (all templates): Table showing exact/high/partial/none counts and coverage % per template
|
|
||||||
- **Detail mode** (single template): Table showing each archetype's status, similarity %, and matching file
|
|
||||||
|
|
||||||
**Use cases:**
|
|
||||||
- **Quality assurance**: Ensure templates follow established patterns
|
|
||||||
- **Refactoring**: Identify templates that could benefit from archetype alignment
|
|
||||||
- **Documentation**: Track which archetypes are most/least used across templates
|
|
||||||
|
|
||||||
### Template Development Workflow
|
|
||||||
|
|
||||||
1. **Discover**: Use `list` command to see available archetype components for your module
|
|
||||||
2. **Review**: Preview archetypes to understand implementation patterns
|
|
||||||
3. **Copy**: Copy relevant archetype components to your template directory
|
|
||||||
4. **Customize**: Modify as needed (hardcode image, add custom labels, etc.)
|
|
||||||
5. **Validate**: Use `compose validate` to check Jinja2 syntax and semantic correctness
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
**Key Concepts:**
|
|
||||||
- Each module can have its own `archetypes/<module>/` directory with reusable components
|
|
||||||
- Components are modular Jinja2 files that can be tested in isolation or composition
|
|
||||||
- **Testing only**: The `generate` command NEVER writes files - always shows preview output
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
- Merges variable sources: module spec → archetypes.yaml → CLI --var
|
|
||||||
- Renders using Jinja2 with support for `{% include %}` directives
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Local setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
python3 -m pip install -e ".[test]" ruff
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m cli
|
||||||
|
python3 -m cli --log-level DEBUG compose list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation gates
|
||||||
|
|
||||||
|
Run the relevant checks before finishing changes. These are release gates, not optional cleanup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ruff check --fix .
|
||||||
|
ruff format .
|
||||||
|
python3 -m pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
If `ruff` is not installed in the active environment, install it before running the gates.
|
||||||
|
|
||||||
|
For production-like boilerplate testing before a release, see `WARP-LOCAL.md` if it exists locally. It is intentionally not tracked in git and may describe local Docker contexts, test servers, and release-readiness checklists.
|
||||||
|
|
||||||
|
## GitHub workflow
|
||||||
|
|
||||||
|
This repository uses GitHub issues, branches, pull requests, and releases.
|
||||||
|
|
||||||
|
Before GitHub-facing work such as issues, branches, PRs, merges, tags, or releases, read:
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- this contribution guide
|
||||||
|
- `RELEASE.md` for release-related work
|
||||||
|
|
||||||
|
## Naming conventions
|
||||||
|
|
||||||
|
- Branches and PRs: `feature/2314-add-feature`, `problem/1249-fix-bug`, `release/x.x.x`
|
||||||
|
- Commit messages: `type(scope): subject`, for example `fix(compose): correct variable parsing`
|
||||||
|
- Issues should have clear titles and descriptions, link related issues/PRs, and use appropriate labels such as `problem`, `feature`, `discussion`, or `question`.
|
||||||
|
|
||||||
|
## Standard commands
|
||||||
|
|
||||||
|
Standard module commands are auto-registered for all modules:
|
||||||
|
|
||||||
|
- `list` - list templates
|
||||||
|
- `search <query>` - search templates by ID
|
||||||
|
- `show <id>` - show template details
|
||||||
|
- `generate <id>` - generate from a template; supports `--dry-run`, `--var`, `--var-file`, `--no-interactive`, and destination options
|
||||||
|
- `validate [template_id]` - validate one or all templates
|
||||||
|
- `defaults` - manage config defaults with `get`, `set`, `rm`, `clear`, and `list`
|
||||||
|
|
||||||
|
Core repository commands include:
|
||||||
|
|
||||||
|
- `repo update` - sync git-based libraries
|
||||||
|
- `repo list` - list configured libraries
|
||||||
|
- `repo add` / `repo remove` - manage configured libraries
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
# Release Process
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Releases are prepared through a release PR and published by pushing a version tag. The GitHub Actions release workflow runs on tags matching `v*.*.*`.
|
||||||
|
|
||||||
|
The release PR should be limited to release preparation changes whenever possible: version files and changelog updates.
|
||||||
|
|
||||||
|
## Version files
|
||||||
|
|
||||||
|
The release version must match in all of these places:
|
||||||
|
|
||||||
|
- `pyproject.toml`
|
||||||
|
- `cli/__init__.py`
|
||||||
|
- Git tag, e.g. `vx.x.x`
|
||||||
|
|
||||||
|
The release workflow validates this and fails if they differ.
|
||||||
|
|
||||||
|
## Prepare a release
|
||||||
|
|
||||||
|
1. Start from an updated `main` branch.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
git checkout -b release/x.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update version files if needed.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# pyproject.toml
|
||||||
|
version = "x.x.x"
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# cli/__init__.py
|
||||||
|
__version__ = "x.x.x"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Finalize `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Move entries from:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## [Unreleased]
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## [x.x.x] - YYYY-MM-DD
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add a fresh empty section above it:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## [Unreleased]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run release gates.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ruff check --fix .
|
||||||
|
ruff format .
|
||||||
|
python3 -m pytest
|
||||||
|
cubic review -j
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Commit and push.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add pyproject.toml cli/__init__.py CHANGELOG.md
|
||||||
|
git commit -m "release: prepare vx.x.x"
|
||||||
|
git push -u origin release/x.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Open a PR into `main`.
|
||||||
|
|
||||||
|
PR title:
|
||||||
|
|
||||||
|
```text
|
||||||
|
release: prepare vx.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Merge the release PR after checks and review pass.
|
||||||
|
|
||||||
|
## Publish the release
|
||||||
|
|
||||||
|
After the release PR is merged:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
git tag vx.x.x
|
||||||
|
git push origin vx.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
Pushing the tag triggers `.github/workflows/release-create-cli-release.yaml`.
|
||||||
|
|
||||||
|
The workflow will:
|
||||||
|
|
||||||
|
- validate version consistency
|
||||||
|
- build the Python package
|
||||||
|
- run package checks
|
||||||
|
- create the GitHub Release
|
||||||
|
- attach `.whl` and `.tar.gz` assets
|
||||||
|
- use the matching changelog section as release notes
|
||||||
|
|
||||||
|
## Hotfix releases
|
||||||
|
|
||||||
|
For hotfix/post releases, use the repository's existing tag format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vx.x.x-n
|
||||||
|
```
|
||||||
|
|
||||||
|
The release workflow creates package-compatible asset aliases for these versions.
|
||||||
|
|
||||||
|
## After release
|
||||||
|
|
||||||
|
Verify that:
|
||||||
|
|
||||||
|
- the GitHub Release exists
|
||||||
|
- release assets are attached
|
||||||
|
- the installer can install the release
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/christianlempa/boilerplates/main/scripts/install.sh |
|
||||||
|
bash -s -- --version vx.x.x
|
||||||
|
```
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Architecture Notes
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
- `cli/` - Python CLI application source code
|
||||||
|
- `cli/core/` - core runtime packages
|
||||||
|
- `cli/modules/` - technology-specific module registrations
|
||||||
|
- `cli/__main__.py` - CLI entry point; discovers modules and registers commands
|
||||||
|
- `tests/` - automated tests
|
||||||
|
- `docs/` - developer documentation
|
||||||
|
- `scripts/` - helper scripts, including installer tooling
|
||||||
|
- `library/` - legacy in-repo template content for versions older than `0.2.0`; do not treat it as the canonical template library for modern work
|
||||||
|
- `.github/` - workflows, templates, and repository automation
|
||||||
|
|
||||||
|
## Supported kinds
|
||||||
|
|
||||||
|
The supported kinds are the registered modules in `cli/modules/`. Prefer this code-level list over legacy content in `library/`.
|
||||||
|
|
||||||
|
Current kinds:
|
||||||
|
|
||||||
|
- `ansible`
|
||||||
|
- `bash`
|
||||||
|
- `compose`
|
||||||
|
- `helm`
|
||||||
|
- `kubernetes`
|
||||||
|
- `packer`
|
||||||
|
- `python`
|
||||||
|
- `static`
|
||||||
|
- `swarm`
|
||||||
|
- `terraform`
|
||||||
|
|
||||||
|
## Module system
|
||||||
|
|
||||||
|
Modules subclass `Module` from `cli/core/module/` and register themselves with the central registry.
|
||||||
|
|
||||||
|
Minimal module example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from ...core.module import Module
|
||||||
|
from ...core.registry import registry
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleModule(Module):
|
||||||
|
name = "example"
|
||||||
|
description = "Manage example templates"
|
||||||
|
|
||||||
|
|
||||||
|
registry.register(ExampleModule)
|
||||||
|
```
|
||||||
|
|
||||||
|
Discovery and registration flow:
|
||||||
|
|
||||||
|
1. `cli/__main__.py` imports module packages/files from `cli/modules/`.
|
||||||
|
2. Each module calls `registry.register(ModuleClass)` at import time.
|
||||||
|
3. The registry stores module classes by module name.
|
||||||
|
4. The CLI framework creates standard commands for every registered module.
|
||||||
|
5. Modules are instantiated on demand when commands run.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
- No central manual registration for new kinds
|
||||||
|
- Modules are self-contained
|
||||||
|
- Registry validation catches invalid module classes early
|
||||||
|
|
||||||
|
## Core components
|
||||||
|
|
||||||
|
Important packages/files:
|
||||||
|
|
||||||
|
- `cli/core/module/` - base module class and standard command implementations
|
||||||
|
- `cli/core/template/` - template manifest parsing, variable normalization, rendering, and validation
|
||||||
|
- `cli/core/config/` - user configuration loading, saving, validation, and migration
|
||||||
|
- `cli/core/library.py` - template discovery across git/static libraries
|
||||||
|
- `cli/core/repo.py` - repository/library management commands
|
||||||
|
- `cli/core/registry.py` - module registry
|
||||||
|
- `cli/core/display/` - centralized CLI output rendering; see `docs/display.md`
|
||||||
|
- `cli/core/validation/` - dependency matrix and kind-specific rendered-file validators
|
||||||
|
- `cli/core/validators.py` - generic content validators such as YAML and Docker Compose validation
|
||||||
|
- `cli/core/exceptions.py` - custom exceptions; prefer these for user-facing errors
|
||||||
|
|
||||||
|
## Prompt/input flow
|
||||||
|
|
||||||
|
Interactive prompting is implemented with Rich-based input helpers under `cli/core/input/`.
|
||||||
|
|
||||||
|
Prompt support includes:
|
||||||
|
|
||||||
|
- text input
|
||||||
|
- password/secret input
|
||||||
|
- yes/no confirmation
|
||||||
|
- numbered choice selection
|
||||||
|
- defaults from template/config/CLI sources
|
||||||
|
- autogenerated secret placeholders
|
||||||
|
|
||||||
|
Use `--no-interactive` to skip prompts and use defaults, config values, CLI overrides, or empty values as appropriate.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Display and Output Rules
|
||||||
|
|
||||||
|
## Critical rule
|
||||||
|
|
||||||
|
User-facing command output should go through `DisplayManager` or one of the display classes under `cli/core/display/`.
|
||||||
|
|
||||||
|
Do not:
|
||||||
|
|
||||||
|
- call `console.print()` from command, module, config, repository, validation, or template-rendering code
|
||||||
|
- import `Console` from `rich.console` for general command output outside display classes or `cli/__main__.py`
|
||||||
|
- use direct emojis or icons in feature code
|
||||||
|
- bypass display helpers for status, errors, warnings, tables, or template output
|
||||||
|
|
||||||
|
Do:
|
||||||
|
|
||||||
|
- use `module_instance.display` in module/command code
|
||||||
|
- use `DisplayManager()` when standalone core command code needs output
|
||||||
|
- add or adjust display helpers in `cli/core/display/` when new output patterns are needed
|
||||||
|
- add shortcode/icon mappings in the display/icon layer
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
Centralized display handling keeps CLI output consistent and makes it easier to manage:
|
||||||
|
|
||||||
|
- formatting standards
|
||||||
|
- quiet/verbose behavior
|
||||||
|
- stderr/stdout routing
|
||||||
|
- icon and Nerd Font usage
|
||||||
|
- table rendering
|
||||||
|
- error/warning/success styles
|
||||||
|
|
||||||
|
## Display architecture
|
||||||
|
|
||||||
|
`DisplayManager` acts as a facade over focused display components.
|
||||||
|
|
||||||
|
Current responsibilities include:
|
||||||
|
|
||||||
|
- variable rendering and variable tables
|
||||||
|
- template metadata and file-tree rendering
|
||||||
|
- status, error, warning, success, and info messages
|
||||||
|
- data/status/config tables
|
||||||
|
- markdown/description rendering
|
||||||
|
|
||||||
|
External code should call facade methods on `DisplayManager`. Internally, the facade delegates to specialized display classes.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
display = DisplayManager()
|
||||||
|
display.success("Template generated")
|
||||||
|
display.error("Validation failed", details="Missing variable")
|
||||||
|
```
|
||||||
|
|
||||||
|
If a needed display method does not exist, add one to the display layer instead of printing directly from feature code.
|
||||||
|
|
||||||
|
Prompt/input internals under `cli/core/input/` may use Rich prompt primitives directly when implementing interactive input behavior. Keep regular command output in the display layer.
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Library and Configuration Notes
|
||||||
|
|
||||||
|
## Canonical template library
|
||||||
|
|
||||||
|
The canonical template library does **not** live in this repository for modern development.
|
||||||
|
|
||||||
|
- The checked-in `library/` directory is legacy/backward-compatibility content for versions older than `0.2.0`.
|
||||||
|
- For `0.2.0+`, active template work belongs in the separate GitHub repository `boilerplates-library`.
|
||||||
|
|
||||||
|
When answering questions about supported kinds or current template behavior, prefer code under `cli/modules/` and runtime logic under `cli/core/` over legacy `library/` content.
|
||||||
|
|
||||||
|
## LibraryManager
|
||||||
|
|
||||||
|
`LibraryManager` loads configured libraries and discovers templates by kind.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- load library configuration
|
||||||
|
- resolve git and static library paths
|
||||||
|
- discover template directories containing `template.json`
|
||||||
|
- normalize template IDs from manifest slugs
|
||||||
|
- handle duplicate template IDs
|
||||||
|
- preserve library priority order
|
||||||
|
|
||||||
|
Git libraries are stored under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.config/boilerplates/libraries/{name}/
|
||||||
|
```
|
||||||
|
|
||||||
|
Git-based libraries use sparse checkout to avoid cloning unrelated repository content where possible.
|
||||||
|
|
||||||
|
## Library types
|
||||||
|
|
||||||
|
### Git libraries
|
||||||
|
|
||||||
|
Git libraries require:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `type: git`
|
||||||
|
- `url`
|
||||||
|
- `branch`
|
||||||
|
- `directory`
|
||||||
|
|
||||||
|
### Static libraries
|
||||||
|
|
||||||
|
Static libraries require:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `type: static`
|
||||||
|
- `path`
|
||||||
|
|
||||||
|
Static library paths may be absolute or relative to the config file location. Some config entries may include backward-compatible dummy `url`, `branch`, and `directory` fields.
|
||||||
|
|
||||||
|
## Priority and duplicate handling
|
||||||
|
|
||||||
|
Library priority is determined by config order. Earlier entries have higher priority.
|
||||||
|
|
||||||
|
Duplicate behavior:
|
||||||
|
|
||||||
|
- duplicate IDs within the same library raise a duplicate-template error
|
||||||
|
- duplicate IDs across libraries can be addressed with qualified IDs
|
||||||
|
- simple IDs resolve to the highest-priority library
|
||||||
|
- qualified IDs target a specific library, for example `alloy.default` or `alloy.local`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
boilerplates compose show alloy
|
||||||
|
boilerplates compose show alloy.local
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config location
|
||||||
|
|
||||||
|
User config is stored at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.config/boilerplates/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Example library config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
libraries:
|
||||||
|
- name: default
|
||||||
|
type: git
|
||||||
|
url: https://github.com/user/templates.git
|
||||||
|
branch: main
|
||||||
|
directory: library
|
||||||
|
- name: local
|
||||||
|
type: static
|
||||||
|
path: ~/my-templates
|
||||||
|
url: ''
|
||||||
|
branch: main
|
||||||
|
directory: .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository commands
|
||||||
|
|
||||||
|
Common library commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
boilerplates repo list
|
||||||
|
boilerplates repo update
|
||||||
|
boilerplates repo add local --type static --path ~/my-templates
|
||||||
|
boilerplates repo remove local
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `DisplayManager` for all output in repository/config code. Do not print directly.
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# Template Authoring Notes
|
||||||
|
|
||||||
|
Templates are directory-based. Each template directory contains `template.json` plus a required `files/` directory with renderable content.
|
||||||
|
|
||||||
|
## Template loading and rendering flow
|
||||||
|
|
||||||
|
1. `LibraryManager` finds template directories containing `template.json`.
|
||||||
|
2. `Template` loads and validates manifest metadata.
|
||||||
|
3. Manifest variables are normalized into the runtime variable collection shape.
|
||||||
|
4. Defaults are merged from template, user config, var-files, and CLI overrides.
|
||||||
|
5. Sections are sorted and dependency constraints are validated.
|
||||||
|
6. Variables are resolved dynamically according to `needs` constraints.
|
||||||
|
7. Files under `files/` render through the custom-delimiter Jinja environment.
|
||||||
|
8. Rendered output is sanitized.
|
||||||
|
9. Optional semantic or kind-specific validation runs.
|
||||||
|
|
||||||
|
## Template structure
|
||||||
|
|
||||||
|
Minimal `template.json` shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"slug": "my-template",
|
||||||
|
"kind": "compose",
|
||||||
|
"metadata": {
|
||||||
|
"name": "My Template",
|
||||||
|
"description": "A template description.",
|
||||||
|
"version": {
|
||||||
|
"name": "v1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "general",
|
||||||
|
"title": "General",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "service_name",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Service name",
|
||||||
|
"default": "demo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template delimiters
|
||||||
|
|
||||||
|
For `0.2.0+`, templates use custom Jinja-like delimiters:
|
||||||
|
|
||||||
|
- Variables: `<< variable_name >>`
|
||||||
|
- Blocks: `<% if condition %> ... <% endif %>`
|
||||||
|
- Comments: `<# comment #>`
|
||||||
|
|
||||||
|
Default Jinja delimiters such as `{{ variable }}` are intentionally left untouched so downstream tools like Ansible can use them literally.
|
||||||
|
|
||||||
|
Legacy `.j2` behavior is no longer supported for new `template.json` templates. Renderable content lives under `files/`, and output paths match the relative paths under `files/`.
|
||||||
|
|
||||||
|
## Metadata versioning
|
||||||
|
|
||||||
|
`metadata.version` is a structured object and may be partial. Supported fields include:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `source_dep_name`
|
||||||
|
- `source_dep_version`
|
||||||
|
- `source_dep_digest`
|
||||||
|
- `upstream_ref`
|
||||||
|
- `notes`
|
||||||
|
|
||||||
|
This separates the user-facing template version label from tracked upstream dependency metadata.
|
||||||
|
|
||||||
|
## Application versions
|
||||||
|
|
||||||
|
Application/image versions should generally be hardcoded in template files, for example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: nginx:1.25.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not create variables such as `nginx_version` unless there is a strong technical reason, such as multi-component version pinning. Hardcoding tested versions avoids mismatches between variables and validated template output.
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
Variable precedence, lowest to highest:
|
||||||
|
|
||||||
|
1. Template defaults from `template.json`
|
||||||
|
2. User config defaults
|
||||||
|
3. Var-file values
|
||||||
|
4. CLI `--var` overrides
|
||||||
|
|
||||||
|
Variable types:
|
||||||
|
|
||||||
|
- `str`
|
||||||
|
- `int`
|
||||||
|
- `float`
|
||||||
|
- `bool`
|
||||||
|
- `email`
|
||||||
|
- `url`
|
||||||
|
- `hostname`
|
||||||
|
- `enum`
|
||||||
|
- `secret`
|
||||||
|
|
||||||
|
Common variable properties:
|
||||||
|
|
||||||
|
- `default` - default value
|
||||||
|
- `title` - prompt/display title
|
||||||
|
- `description` - description/help text
|
||||||
|
- `required` - require non-empty value
|
||||||
|
- `needs` - conditional visibility constraint
|
||||||
|
- `config.options` - enum options
|
||||||
|
- `config.placeholder` - input placeholder
|
||||||
|
- `config.autogenerated` - secret autogeneration settings
|
||||||
|
|
||||||
|
## Sections and dependencies
|
||||||
|
|
||||||
|
Variable groups become sections. Sections can define:
|
||||||
|
|
||||||
|
- `toggle` - auto-created boolean variable that enables/disables the section
|
||||||
|
- `needs` - section dependency by section key
|
||||||
|
|
||||||
|
Variable-level `needs` constraints use this format:
|
||||||
|
|
||||||
|
- `var_name=value`
|
||||||
|
- `var_name!=value`
|
||||||
|
- `var1=value1;var2=value2`
|
||||||
|
- `network_mode=bridge,macvlan` where comma means OR
|
||||||
|
|
||||||
|
Dependency validation detects missing dependencies, circular dependencies, and self-dependencies.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Template validation includes:
|
||||||
|
|
||||||
|
- manifest shape validation
|
||||||
|
- custom-delimiter Jinja syntax validation
|
||||||
|
- undeclared variable detection
|
||||||
|
- rendering validation
|
||||||
|
|
||||||
|
Generic semantic validation is provided by `cli/core/validators.py`, including YAML and Docker Compose-oriented checks where applicable.
|
||||||
|
|
||||||
|
Kind-specific validation lives under `cli/core/validation/` and can be enabled with module validation options such as `--kind` or matrix validation where supported.
|
||||||
|
|
||||||
|
## Docker Compose notes
|
||||||
|
|
||||||
|
When using Traefik with Docker Compose and multiple networks, include the Traefik network label so Traefik routes through the intended network:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
traefik.docker.network: << traefik_network >>
|
||||||
|
```
|
||||||
|
|
||||||
|
For Swarm/Compose-specific label placement, follow existing template patterns before introducing new ones.
|
||||||
|
|
||||||
|
## Shortcodes
|
||||||
|
|
||||||
|
Template descriptions may use emoji-style shortcodes such as `:warning:`, `:info:`, or `:docker:`. Display code maps these to Nerd Font icons. Add new shortcodes in the display/icon layer, not directly in template rendering code.
|
||||||
Reference in New Issue
Block a user