From f64137a60fd75c4bdc27b9f110283778237296f2 Mon Sep 17 00:00:00 2001 From: xcad Date: Wed, 22 Apr 2026 17:19:02 +0200 Subject: [PATCH] wiki updates and file cleanup --- .editorconfig | 26 --- .github/scripts/generate_wiki_docs.py | 252 ------------------------ .github/workflows/docs-update-wiki.yaml | 98 --------- .wiki/Core-Concepts-Defaults.md | 37 ++-- .wiki/Core-Concepts-Libraries.md | 34 +++- .wiki/Core-Concepts-Templates.md | 134 +++++++------ .wiki/Core-Concepts-Variables.md | 104 ++++++---- .wiki/Home.md | 4 - .wiki/Installation.md | 7 +- .wiki/Variables.md | 35 ---- .wiki/_Sidebar.md | 3 - README.md | 10 +- cli/core/template/template.py | 2 - 13 files changed, 190 insertions(+), 556 deletions(-) delete mode 100644 .editorconfig delete mode 100755 .github/scripts/generate_wiki_docs.py delete mode 100644 .github/workflows/docs-update-wiki.yaml delete mode 100644 .wiki/Variables.md diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 63077ab5..00000000 --- a/.editorconfig +++ /dev/null @@ -1,26 +0,0 @@ -# https://editorconfig.org/ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.json] -indent_size = 2 - -[*.{js,jsx,ts,tsx}] -indent_size = 2 - -[*.md] -indent_size = unset -trim_trailing_whitespace = false - -[*.py] -indent_size = 4 - -[{*.{yaml,yml},.yamllint}] -indent_size = 2 diff --git a/.github/scripts/generate_wiki_docs.py b/.github/scripts/generate_wiki_docs.py deleted file mode 100755 index c43a9b0f..00000000 --- a/.github/scripts/generate_wiki_docs.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python3 -"""Generate GitHub Wiki documentation for module variables. - -This script auto-generates variable documentation in GitHub Wiki markdown format -for all registered modules, using the latest schema version for each. -""" - -import sys -from pathlib import Path - -# Add project root to path (script is in .github/scripts, so go up twice) -project_root = Path(__file__).parent.parent.parent -sys.path.insert(0, str(project_root)) - -# ruff: noqa: E402 -# Import all modules to register them -import cli.modules.ansible -import cli.modules.compose -import cli.modules.helm -import cli.modules.kubernetes -import cli.modules.packer -import cli.modules.terraform # noqa: F401 -from cli.core.registry import registry # Module import after path manipulation - - -def format_value(value): - """Format value for markdown display.""" - if value is None or value == "": - return "_none_" - if isinstance(value, bool): - return "✓" if value else "✗" - if isinstance(value, list): - return ", ".join(f"`{v}`" for v in value) - return f"`{value}`" - - -def generate_module_docs(module_name: str, output_dir: Path): # noqa: PLR0912, PLR0915 - """Generate wiki documentation for a single module.""" - # Get module class from registry - module_classes = dict(registry.iter_module_classes()) - - if module_name not in module_classes: - sys.stderr.write(f"Warning: Module '{module_name}' not found, skipping\n") - return False - - module_cls = module_classes[module_name] - schema_version = module_cls.schema_version - - # Get the spec for the latest schema version - if hasattr(module_cls, "schemas") and schema_version in module_cls.schemas: - spec = module_cls.schemas[schema_version] - elif hasattr(module_cls, "spec"): - spec = module_cls.spec - else: - sys.stderr.write(f"Warning: No spec found for module '{module_name}', skipping\n") - return False - - # Generate markdown content - lines = [] - - # Header - lines.append(f"# {module_name.title()} Variables") - lines.append("") - lines.append(f"**Module:** `{module_name}` ") - lines.append(f"**Schema Version:** `{schema_version}` ") - lines.append(f"**Description:** {module_cls.description}") - lines.append("") - lines.append("---") - lines.append("") - lines.append( - "This page documents all available variables for the " - + f"{module_name} module. Variables are organized into sections " - + "that can be enabled/disabled based on your configuration needs." - ) - lines.append("") - - # Table of contents - lines.append("## Table of Contents") - lines.append("") - for section_key, section_data in spec.items(): - section_title = section_data.get("title", section_key) - anchor = section_title.lower().replace(" ", "-").replace("/", "") - lines.append(f"- [{section_title}](#{anchor})") - lines.append("") - lines.append("---") - lines.append("") - - # Process each section - for section_key, section_data in spec.items(): - section_title = section_data.get("title", section_key) - section_desc = section_data.get("description", "") - section_toggle = section_data.get("toggle", "") - section_needs = section_data.get("needs", "") - section_required = section_data.get("required", False) - section_vars = section_data.get("vars", {}) - - # Section header - lines.append(f"## {section_title}") - lines.append("") - - # Section metadata - metadata = [] - if section_required: - metadata.append("**Required:** Yes") - if section_toggle: - metadata.append(f"**Toggle Variable:** `{section_toggle}`") - if section_needs: - if isinstance(section_needs, list): - needs_str = ", ".join(f"`{n}`" for n in section_needs) - else: - needs_str = f"`{section_needs}`" - metadata.append(f"**Depends On:** {needs_str}") - - if metadata: - lines.append(" \n".join(metadata)) - lines.append("") - - if section_desc: - lines.append(section_desc) - lines.append("") - - # Skip sections with no variables - if not section_vars: - lines.append("_No variables defined in this section._") - lines.append("") - continue - - # Variables table - lines.append("| Variable | Type | Default | Description |") - lines.append("|----------|------|---------|-------------|") - - for var_name, var_data in section_vars.items(): - var_type = var_data.get("type", "str") - var_default = format_value(var_data.get("default")) - var_description = var_data.get("description", "").replace("\n", " ") - - # Add extra metadata to description - extra_parts = [] - if var_data.get("sensitive"): - extra_parts.append("**Sensitive**") - if var_data.get("autogenerated"): - extra_parts.append("**Auto-generated**") - if "options" in var_data: - opts = ", ".join(f"`{o}`" for o in var_data["options"]) - extra_parts.append(f"**Options:** {opts}") - if "needs" in var_data: - extra_parts.append(f"**Needs:** `{var_data['needs']}`") - if "extra" in var_data: - extra_parts.append(var_data["extra"]) - - if extra_parts: - var_description += "
" + " • ".join(extra_parts) - - lines.append(f"| `{var_name}` | `{var_type}` | {var_default} | {var_description} |") - - lines.append("") - lines.append("---") - lines.append("") - - # Footer - lines.append("## Notes") - lines.append("") - lines.append("- **Required sections** must be configured") - lines.append("- **Toggle variables** enable/disable entire sections") - lines.append("- **Dependencies** (`needs`) control when sections/variables are available") - lines.append("- **Sensitive variables** are masked during prompts") - lines.append("- **Auto-generated variables** are populated automatically if not provided") - lines.append("") - lines.append("---") - lines.append("") - lines.append(f"_Last updated: Schema version {schema_version}_") - - # Write to file - output_file = output_dir / f"Variables-{module_name.title()}.md" - output_file.write_text("\n".join(lines)) - - sys.stdout.write(f"Generated: {output_file.name}\n") - return True - - -def generate_variables_index(modules: list[str], output_dir: Path): - """Generate index page for all variable documentation.""" - lines = [] - - lines.append("# Variables Documentation") - lines.append("") - lines.append("This section contains auto-generated documentation for all " + "available variables in each module.") - lines.append("") - lines.append("## Available Modules") - lines.append("") - - for module_name in sorted(modules): - lines.append(f"- [{module_name.title()}](Variables-{module_name.title()})") - - lines.append("") - lines.append("---") - lines.append("") - lines.append("Each module page includes:") - lines.append("") - lines.append("- Schema version information") - lines.append("- Complete list of sections and variables") - lines.append("- Variable types, defaults, and descriptions") - lines.append("- Section dependencies and toggle configurations") - lines.append("") - lines.append("---") - lines.append("") - lines.append("_This documentation is auto-generated from module schemas._") - - output_file = output_dir / "Variables.md" - output_file.write_text("\n".join(lines)) - - sys.stdout.write(f"Generated: {output_file.name}\n") - - -# Minimum required arguments -MIN_ARGS = 2 - - -def main(): - """Main entry point.""" - if len(sys.argv) < MIN_ARGS: - sys.stderr.write("Usage: python3 scripts/generate_wiki_docs.py \n") - sys.exit(1) - - output_dir = Path(sys.argv[1]) - output_dir.mkdir(parents=True, exist_ok=True) - - sys.stdout.write(f"Generating wiki documentation in: {output_dir}\n") - sys.stdout.write("\n") - - # Get all registered modules - module_classes = dict(registry.iter_module_classes()) - successful_modules = [] - - for module_name in sorted(module_classes.keys()): - if generate_module_docs(module_name, output_dir): - successful_modules.append(module_name) - - sys.stdout.write("\n") - - # Generate index page - if successful_modules: - generate_variables_index(successful_modules, output_dir) - sys.stdout.write("\n") - sys.stdout.write(f"✓ Successfully generated documentation for {len(successful_modules)} module(s)\n") - else: - sys.stderr.write("Error: No documentation generated\n") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/.github/workflows/docs-update-wiki.yaml b/.github/workflows/docs-update-wiki.yaml deleted file mode 100644 index 6e45a61f..00000000 --- a/.github/workflows/docs-update-wiki.yaml +++ /dev/null @@ -1,98 +0,0 @@ ---- -name: Docs - Update Wiki - -'on': - push: - branches: - - main - paths: - - 'cli/core/schema/**/*.json' # JSON schema files - - '.wiki/**' # Static wiki pages - - '.github/scripts/generate_wiki_docs.py' # Wiki generation script - - '.github/workflows/docs-update-wiki.yaml' # This workflow - workflow_dispatch: # Allow manual trigger - -permissions: - contents: write - -jobs: - update-wiki: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Checkout wiki repository - uses: actions/checkout@v6 - with: - repository: ${{ github.repository }}.wiki - path: wiki - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.14' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e . - - - name: Generate variable documentation - run: | - python3 .github/scripts/generate_wiki_docs.py wiki/ - - - name: Sync wiki pages from .wiki directory - run: | - # Copy all markdown files from .wiki/ to wiki/ (except Variables-*.md which are auto-generated) - if [ -d ".wiki" ]; then - echo "Syncing wiki pages from .wiki/ directory..." - for file in .wiki/*.md; do - filename=$(basename "$file") - # Skip auto-generated variable documentation files - if [[ ! "$filename" =~ ^Variables- ]]; then - echo " Copying $filename" - cp "$file" "wiki/$filename" - fi - done - else - echo "No .wiki directory found, skipping static wiki pages sync" - fi - - - name: Check for changes - id: changes - working-directory: wiki - run: | - git add . - if git diff --staged --quiet; then - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "No changes detected in wiki documentation" - else - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "Changes detected in wiki documentation" - fi - - - name: Commit and push changes - if: steps.changes.outputs.has_changes == 'true' - working-directory: wiki - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git commit -m "Auto-update wiki pages" - - # Pull with rebase to handle any remote changes, then push - # GitHub wikis use master as default branch - git pull --rebase origin master - git push origin master - - - name: Summary - run: | - if [ "${{ steps.changes.outputs.has_changes }}" == "true" ]; then - echo "Wiki variable documentation updated successfully" - echo "View at: https://github.com/${{ github.repository }}/wiki" - else - echo "No changes to wiki documentation" - fi diff --git a/.wiki/Core-Concepts-Defaults.md b/.wiki/Core-Concepts-Defaults.md index ff7b2ffc..d9acc55d 100644 --- a/.wiki/Core-Concepts-Defaults.md +++ b/.wiki/Core-Concepts-Defaults.md @@ -4,7 +4,7 @@ Save time by setting default values for variables you use frequently. This page ## What are Default Variables? -**Default variables** are user-defined values that override module and template defaults. They allow you to: +**Default variables** are user-defined values that override template manifest values for a module. They allow you to: - Avoid repetitive typing during template generation - Standardize values across multiple templates - Customize your environment once, use everywhere @@ -13,13 +13,13 @@ Save time by setting default values for variables you use frequently. This page Variables are resolved in this order (lowest to highest priority): -1. Module runtime defaults -2. Template defaults from `template.json` -3. **User config** (your saved defaults) ← This page -4. `--var-file` -5. CLI arguments (`--var`) +1. Template manifest values from `template.json` +2. **User config** (your saved defaults) ← This page +3. `--var-file` +4. CLI arguments (`--var`) +5. Interactive prompt answers -Your saved defaults override module and template defaults, but they can still be overridden at generation time. +Your saved defaults override template manifest values, but they can still be overridden at generation time. ## Managing Defaults @@ -104,7 +104,7 @@ Response: Removed default: container_timezone ``` -The variable will now use module/template defaults again. +The variable will now use template manifest values again. ### Clear All Defaults @@ -123,11 +123,13 @@ Cleared all defaults for compose ## Configuration Storage -Defaults are stored in: +Defaults are usually stored in: ``` ~/.config/boilerplates/config.yaml ``` +If a local `./config.yaml` exists in your current working directory, the CLI uses that file instead. + Example content: ```yaml libraries: @@ -153,13 +155,15 @@ defaults: You can manually edit the config file: ```bash -# Edit configuration +# Edit the global configuration example nano ~/.config/boilerplates/config.yaml # Verify defaults boilerplates compose defaults list ``` +If you are using a local `./config.yaml`, edit that file instead. + ## Common Use Cases ### Timezone Configuration @@ -251,6 +255,8 @@ Defaults don't transfer between modules—they're module-specific. ## Backup and Restore +The examples below use the global config path. If the CLI is using a local `./config.yaml`, substitute that path instead. + ### Backup Configuration Save your configuration: @@ -332,7 +338,7 @@ cat ~/.config/boilerplates/config.yaml If config file is corrupted: ```bash -# Validate YAML syntax +# Validate global config YAML syntax python3 -c "import yaml; yaml.safe_load(open('~/.config/boilerplates/config.yaml'))" # Or remove and recreate @@ -346,15 +352,16 @@ If wrong values appear: ```bash # Check precedence -# 1. Module spec -# 2. Template spec -# 3. User defaults ← Check here +# 1. Template manifest values +# 2. User defaults ← Check here +# 3. --var-file # 4. CLI --var +# 5. Interactive answers # Verify your defaults boilerplates compose defaults list -# Check template spec +# Check the loaded template state boilerplates compose show ``` diff --git a/.wiki/Core-Concepts-Libraries.md b/.wiki/Core-Concepts-Libraries.md index 2c1bf118..67250f7e 100644 --- a/.wiki/Core-Concepts-Libraries.md +++ b/.wiki/Core-Concepts-Libraries.md @@ -40,14 +40,28 @@ libraries: ## Local Storage -Git libraries are stored under: +Git libraries are stored under the active config directory's `libraries/` folder. + +Global-config default: ```text ~/.config/boilerplates/libraries/ ``` +If the CLI is using a local `./config.yaml`, the library checkout location becomes `./libraries/`. + The configured `directory` is applied inside that checkout. For the official library the directory is `.`. +## Configuration Location + +By default, Boilerplates uses: + +```text +~/.config/boilerplates/config.yaml +``` + +If a local `./config.yaml` exists in your current working directory, the CLI uses that file instead. + ## Discovery Rules Boilerplates discovers templates by module directory, for example: @@ -79,11 +93,21 @@ boilerplates repo update Add a custom Git library: ```bash -boilerplates repo add my-templates https://github.com/user/templates \ +boilerplates repo add my-templates \ + --library-type git \ + --url https://github.com/user/templates \ --directory . \ --branch main ``` +Add a static library: + +```bash +boilerplates repo add local \ + --library-type static \ + --path ~/my-templates +``` + Remove a library: ```bash @@ -127,12 +151,6 @@ Templates with `metadata.draft: true` are excluded from normal listings and look ## Config File -Library configuration lives in: - -```text -~/.config/boilerplates/config.yaml -``` - Example: ```yaml diff --git a/.wiki/Core-Concepts-Templates.md b/.wiki/Core-Concepts-Templates.md index f6661401..48133e16 100644 --- a/.wiki/Core-Concepts-Templates.md +++ b/.wiki/Core-Concepts-Templates.md @@ -1,10 +1,8 @@ # Templates -Templates are the core unit of Boilerplates. A template is a directory with a `template.json` manifest and a `files/` directory containing the files to render. +Templates are the core unit of Boilerplates. A supported template is a directory with a `template.json` manifest and a `files/` directory containing every renderable output file. -## Template Structure - -Every supported template looks like this: +## Required Layout ```text my-template/ @@ -16,11 +14,13 @@ my-template/ └── app.yaml ``` -Only `template.json` is a supported manifest format. Legacy `template.yaml` and `template.yml` manifests are not supported. +Rules: +- `template.json` is the only supported manifest format +- all rendered content must live under `files/` +- legacy `template.yaml`, `template.yml`, and top-level `.j2` layouts are incompatible with the current runtime -## Manifest Shape +## Top-Level Manifest Shape -Top-level fields: - `slug` - `kind` - `metadata` @@ -35,6 +35,8 @@ Example: "metadata": { "name": "My Template", "description": "Short human description", + "author": "Your Name", + "date": "2026-04-22", "tags": ["infra", "dev"], "icon": { "provider": "mdi", @@ -48,7 +50,7 @@ Example: "source_dep_version": "1.1.0", "source_dep_digest": "sha256:abc123def456", "upstream_ref": "release-2026-04-22", - "notes": "Tracks upstream container release used by this template snapshot" + "notes": "Tracks the tested upstream dependency snapshot" } }, "variables": [ @@ -68,24 +70,38 @@ Example: } ``` +## `slug` and Template IDs + +`slug` is the canonical template ID exposed by the CLI. + +Behavior: +- if `slug` is present, it wins over the directory name +- if `slug` ends with `-`, that suffix is normalized away for CLI use +- if `slug` is missing, the directory name is used + +Example: +- directory: `portainer/` +- `kind`: `compose` +- `slug`: `portainer-compose` +- CLI ID: `portainer` + ## Metadata Common metadata fields: - `name` - `description` +- `author` +- `date` - `tags` - `icon` - `draft` -- `author` -- `date` -- `guide` - `version` ### Version Metadata -`metadata.version` is optional. If present, it must be an object. +`metadata.version` is optional, but when present it must be an object. -Supported version fields: +Supported fields: - `name` - `source_dep_name` - `source_dep_version` @@ -93,57 +109,34 @@ Supported version fields: - `upstream_ref` - `notes` -Important rules: -- the whole `version` object may be omitted -- any individual `version` field may be omitted -- the CLI uses `metadata.version.name` as the visible version label in list/show output +Important behavior: +- `metadata.version.name` is the user-facing version label shown in list/show output +- the rest of the version object is for upstream dependency tracking +- the full object may be omitted +- individual fields inside the object may also be omitted -This means these are all valid: +## Variable Declarations Are Mandatory -```json -{ - "metadata": { - "name": "My Template" - } -} -``` +Any variable used in files under `files/` must be declared in `template.json`. -```json -{ - "metadata": { - "name": "My Template", - "version": { - "name": "v1.1" - } - } -} -``` +If a file references an undeclared variable, the template fails validation and load/render operations surface a template error. -```json -{ - "metadata": { - "name": "My Template", - "version": { - "source_dep_name": "ghcr.io/example/my-image", - "source_dep_version": "1.1.0" - } - } -} -``` +Use the [Variables](Core-Concepts-Variables) page for the manifest structure. ## Files and Rendering -All files inside `files/` are part of the template output. +Everything under `files/` is part of the output tree. -Rendering rules: -- Boilerplates renders every file under `files/` -- files without template expressions pass through unchanged -- output paths currently match the relative file paths under `files/` -- template discovery ignores anything without `template.json` +Rendering behavior: +- Boilerplates walks every file under `files/` +- files are rendered with the custom delimiter set +- files without template expressions still pass through the render pipeline +- output paths currently mirror the relative paths inside `files/` +- rendered output is sanitized to normalize blank lines and trailing whitespace ## Delimiters -Templates use custom delimiters, not default Jinja syntax: +Templates use custom delimiters rather than default Jinja syntax: - variables: `<< value >>` - blocks: `<% if condition %>` @@ -165,17 +158,21 @@ Legacy `{{ }}`, `{% %}`, and `{# #}` delimiters are rejected. ## Includes and Imports -Includes and imports are resolved relative to the template's `files/` directory. - -Example: +Includes and imports resolve relative to the template's `files/` directory. ```jinja <% include 'partials/header.yaml' %> ``` -## Template Discovery +## Discovery Rules -Templates are discovered from configured libraries. A directory is considered a template only when it contains `template.json`. +Templates are discovered from configured libraries. + +A directory is considered a valid template only when: +- `template.json` exists +- `files/` exists + +In practice, Boilerplates discovers templates by module directory path such as `compose/