template-engine-copier skill: initial import

Copier + Jinja template-repository design skill: reference architecture,
implementation workflow, validation checklist, and a full worked example
template (questionnaire, extensions, resolvers, wrapper-first .scripts).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 22:30:29 +03:00
co-authored by Claude Opus 5
commit 97991021ff
29 changed files with 932 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
# Implementation Workflow (Copier + Jinja Template Repository)
Follow this sequence when creating a new template-engine repository.
## Phase 1 — Define rendering contract
1. Define template scope (what downstream repository you generate).
2. Identify stable input schema sections (e.g. tenant/project/environments/resources).
3. Decide what is:
- static files,
- conditional files/folders,
- repeatable per-instance blocks.
Deliverable: first draft of `copier-answers.example.yaml`.
## Phase 2 — Bootstrap Copier skeleton
Create:
- `.copier/copier.yaml`
- `.copier/questionnaire/*.yml`
- `{{ _copier_conf.answers_file }}.jinja`
Use baseline:
- `_template_suffix: ".jinja"`
- modular `!include .copier/questionnaire/*.yml`
- explicit `_exclude` list
- `_answers_file: copier-answers.yml`
## Phase 3 — Create operational wrappers
Implement wrapper chain:
1. root `Taskfile.yml` includes `.scripts/copier/Taskfile.yml`
2. `.scripts/copier/Taskfile.yml` exposes `bootstrap-one`
3. `.scripts/copier/api/bootstrap-one.sh` remains thin
4. **confirm ENV var prefix with user** (or propose one) before implementing lib internals
5. `.scripts/copier/lib/` contains logic and answers merge helpers
Minimum behavior in lib:
- parse CLI args with both styles: `--arg value` and `--arg=value` (for `--copier-target-dir`, `--copier-force-copy`, `--copier-params-file`),
- prepare merged answers file,
- run `copier copy --trust --data-file ...` from a sibling temporary copy of the template repo, not from the live template repo folder,
- post-render sanitization/renames if needed.
Mandatory temp-copy bootstrap pattern from `iac-tmpl-aws-projects`:
- In `api/bootstrap-one.sh`:
1. derive the live template folder name from `basename "$PWD"`,
2. delete any old sibling `../tmp-${TEMPLATE_FOLDER}`,
3. copy the whole template repo into `../tmp-${TEMPLATE_FOLDER}`,
4. `cd` into the temp folder,
5. source `.scripts/loggers/lib/--index.sh` and `.scripts/copier/lib/--index.sh`,
6. execute `_copier_bootstrap_one`,
7. return to the original live template folder.
- In `lib/-bootstrap-one.sh`, before `copier copy`:
1. copy `.copier/copier.yaml` to root `copier.yaml` if the repo uses `.copier/copier.yaml`,
2. remove `.git`, `.devbox`, and `.venv` from the temp copy,
3. run Copier from the temp folder into the real target.
Do **not** create Git tags merely to silence Copier's local template version warning. The preferred local bootstrap hygiene is rendering from the sanitized temp copy. Use `--vcs-ref=HEAD` only when there is an explicit reason to render directly from a Git-backed live folder.
## Phase 4 — Add Jinja template layers
1. Add top-level templates (`iac/main.tf.jinja`, providers, backend, etc.).
2. Gate directories via folder-name expressions:
- `{{ 'dev' if flat.dev_is_enabled }}`
3. Gate module calls inside files via `{% if ... %}`.
Pattern:
- folder condition controls existence,
- inner condition controls file content clarity.
**Mandatory wrapped render-path rule:**
- For every optional module/resource, apply conditions at the **path level** (folder/file names), not only inside file contents.
- Preferred pattern (from `iac-tmpl-aws-projects`):
- `iac/{{ 'dev' if flat.dev_is_enabled }}/{{ 'compute' if flat.dev_compute_is_enabled }}/main.tf.jinja`
- Apply this systematically to all optional Jinja outputs so disabled features do not produce placeholder files/directories.
**Mandatory safe-variable rule:**
- Do not embed repeated normalization/sanitization logic in template paths or file bodies, e.g. avoid:
- `{{ project.name | lower | replace(' ', '-') | replace('_', '-') | replace('.', '-') }}`
- If a safe name is needed, calculate it once in the context hook and expose a named variable, e.g.:
- `flat.project_safe`
- `flat.environment_safe`
- `flat.component_safe_names`
- Use the named variable in templates:
- `applications/{{ flat.project_safe }}/main.yaml.jinja`
- If a one-off transformation is genuinely needed, prefer a small deterministic resolver over duplicating filter chains.
## Phase 5 — Add custom extensions (if complexity requires)
When logic gets repetitive, move it into extensions.
### Resolver extension examples
- normalize names: `resolve_safe_tf_module_name(name)`
- derive tags / regions / zones
### Context hook extension examples
- calculate `flat.*` shortcuts,
- calculate safe/normalized names such as `flat.project_safe` and `flat.environment_safe`,
- expand `[INDEX]` directories into per-instance folders.
Register in `.copier/copier.yaml` under `_jinja_extensions`.
## Phase 6 — Implement answers merge strategy
Use this exact order to avoid destructive updates:
1. `copier-answers-new.yml` create if missing,
2. copy `<target>/copier-answers.yml` if present,
3. merge params file into working answers file (`yq eval-all ...`).
Then run copier using merged file.
## Phase 7 — Handle template/self-repo conflicts
If template repo contains files that should not be copied 1:1 (e.g. self CI):
1. render as temp name (`.tmp-gitlab-ci.yml`),
2. rename in target during bootstrap.
## Phase 8 — Add docs and examples
Add/update:
- `README.md` with local setup and bootstrap command,
- `copier-answers.example.yaml` with realistic schema examples,
- optional `bootstrap-one-test` task for local maintainer testing.
## Phase 9 — Validate end-to-end
Run two critical scenarios:
1. fresh render into empty target,
2. re-render into existing target with existing `copier-answers.yml`.
Both must succeed without manual edits.
## Quick-start from this skill example
If you want to accelerate implementation, copy `example/` and then:
1. expand questionnaire files,
2. expand `flat.*` derivation logic in context hook,
3. add domain-specific resolver extensions,
4. extend `iac/**/*.jinja` resource tree,
5. keep bootstrap/answers flow unchanged unless you have a strong reason.