Files
template-engine-copier-3darch/instructions/02-implementation-workflow.md
root-at-skicandClaude Opus 5 97991021ff 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>
2026-08-12 22:30:29 +03:00

5.8 KiB

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.