Preliminary study — Repository generation engine from templates¶
Project: Templetry — a manager for architectures, projects and frameworks Date: August 2026 · Version: 1.1 · Status: preliminary study, no implementation v1.1 changes: multi-forge — target-repo creation must work on any open git host, not only GitHub (FR9, D9).
1. Goal and scope¶
Build the engine that generates a new repository's content from a template, within these scope limits:
- In: multi-platform template rendering (Android, KMP, web, backend, etc.), feature composition, output verification, local CLI.
- Out (later phases): web app, GitHub OAuth, repo creation via API, visual catalog. The engine must not depend on any of these.
- Target user: initially one (the author); the design stays open to third parties.
Engine success metric: from command to compilable project in < 30 seconds (excluding verify), for any registered template, with zero framework-specific code in the engine.
2. Requirements¶
Functional¶
| # | Requirement |
|---|---|
| FR1 | Substitute the template's identity (packages, names, slugs) in file content and paths |
| FR2 | Automatically derive casing variants of every variable (Pascal, kebab, snake, flat, camel) |
| FR3 | Conditional inclusion/exclusion of files and blocks based on selected features |
| FR4 | Modify comment-less structured files (JSON) declaratively |
| FR5 | Copy binaries untouched, preserving execute permissions |
| FR6 | Dry-run: show the operation plan without executing anything |
| FR7 | Verify that the output compiles, without toolchains installed on the host |
| FR8 | Record in the output which template, version and values were used (answers file) |
| FR9 | Create the target repo on any open git host (GitHub, GitLab, Gitea/Forgejo, Bitbucket, self-hosted), not only GitHub. The app's login can remain GitHub OAuth |
Non-functional¶
| # | Requirement |
|---|---|
| NFR1 | Agnostic: zero framework knowledge in engine code |
| NFR2 | Templates compile: every template repo is a valid project with its own CI |
| NFR3 | Engine = pure library (directory in → directory out) + CLI on top; no HTTP, no GitHub |
| NFR4 | Deterministic: same inputs → byte-identical output (the basis for golden tests) |
| NFR5 | Maintainable by one person: effort grows with templates, not with the engine |
3. State of the art¶
| Tool | School | What to learn from it | Why not adopt it |
|---|---|---|---|
| Copier | Textual (Jinja2, YAML) | Answers file + three-way-merge updates (the only one managing code lifecycle, not just scaffolding). Form rendering from a YAML questionnaire. | Placeholder templates don't compile; delimiter collisions with Gradle/Kotlin; the manifest (public API) would belong to someone else |
| Cookiecutter | Textual (Jinja2, JSON) | De facto standard; pre/post-gen hooks; 4000+ templates as a reference of real needs | No updates or migrations; same Jinja problems |
| Yeoman / Plop / Hygen | Programmatic (JS) | In-project generators (add a component to an existing repo) — possible future phase | Every template is code to maintain; scales poorly for one person |
| Nx generators | Programmatic (AST) | Virtual file tree (eases dry-run and tests); automated migrations | Coupled to its ecosystem; high complexity |
| Projen | Code-first | "Config is regenerated, never edited" philosophy — anti-drift | Ties templates to its runtime; different mental model |
| Backstage scaffolder | Action pipeline | Declarative manifest that renders forms; catalog/execution separation | Enterprise weight; needs a platform team |
| scaffold / stencil / boilr (Go) | Textual (Go templates) | Single-binary distribution as a CLI format | Conceptually Cookiecutter |
| GitHub Templates | Plain copy | The baseline to beat; the generate API as reference |
No variables, no logic |
Survey conclusion: language coverage is not a differentiator (all textual engines are agnostic by construction). No tool combines compilable templates + feature composition + manifest as API. That gap justifies building our own engine.
4. Design space — decisions¶
D1. Templating school — PROPOSED: hybrid¶
- (a) Textual placeholders (Jinja): maximum expressiveness; templates stop compiling, lose CI and IDE, collide with Gradle/Kotlin
${}. - (b) Programmatic: maximum power; every template is a program. Rejected per NFR5.
- (c) Real project + structured rename: robust and verifiable; no conditionals.
- Proposal: (c) + comment directives + structured patches. Rationale: NFR2 is worth more the more platforms exist — each template's CI verifies what the author can't hand-test across 15 ecosystems.
D2. Where framework knowledge lives — PROPOSED: in the manifest¶
The engine exposes 5 generic operations; template.yml configures them. Supporting a framework = writing a template, never touching the engine. (The anti-decision of Yeoman/Backstage.)
D3. The three transformation mechanisms — PROPOSED¶
- Identity-map renaming (universal): the template declares its canonical strings (
com.template.base,template-app,TemplateApp) and which variable+casing they map to. - Comment directives (
tpl:var,tpl:if/tpl:endif): per-extension comment-style table (~20 entries:// # <!-- --> /* */). Unknown extension → copy without scanning (safe by default). - Structured patches for comment-less formats: JSON Patch RFC 6902 (don't invent a format), extended to YAML/TOML with the same operation vocabulary.
- Documented escape hatch:
*.tplfiles rendered with full textual templating, losing the compile guarantee (exceptional path, not the main one).
D4. Verification — PROPOSED: container declared in the manifest¶
verify: {image, run}. The engine only knows docker run. No toolchains on the host; each template's CI = a matrix over its main feature combinations.
D5. Updates of generated projects — PROPOSED: prepare, don't implement¶
Write .templetry-answers.yml (template, version/commit, values) into every generated project from day 1 — zero cost, retroactively indispensable. Copier-style three-way merge stays for a future phase; it is the expensive piece (months).
D6. Engine language — OPEN¶
| Criterion | Kotlin | TypeScript | Go |
|---|---|---|---|
| Author affinity | ★★★ | ★★ | ★ |
| Code sharing with the future web | via Ktor backend | ★★★ (same stack) | ✗ |
| CLI distribution | JVM or GraalVM native | requires Node | ★★★ single binary |
| Libraries (casings, JSON Patch, tar, YAML) | ★★ | ★★★ | ★★ |
| Risk/novelty | low | low | medium (new language) |
Note: the engine is a pure library (NFR3), so the language doesn't shape the architecture — it shapes distribution and reuse. Needed before the first prototype, not before the schema.
D7. Exact directive grammar — OPEN¶
Minimum viable: tpl:var <key> (line) and tpl:if <expr> / tpl:endif (block). To decide: tpl:else? expressions (and/or/not) or feature keys only? line vs whole-file directives? Principle: the fewer, the better — every new directive is public API.
D8. Project name — RESOLVED: Templetry¶
Chosen after verifying ~70 candidates across 6 rounds (August 2026). Derived from templet, the original spelling of "template" (and a real loom tool). Verified: GitHub (templetry, exact org) and npm free; no existing product with that name; enough distance from Templafy/Templify.
Consequences: CLI templetry (short alias tpl), answers file .templetry-answers.yml, directive prefix tpl: (brand-consistent). Fully-available rejected finalists, kept as plan B: Repojig, Forgehand.
D9. Multi-forge (FR9) — PROPOSED: minimal adapters + capabilities + universal fallback¶
The engine is unaffected (NFR3); only the orchestrator. Problem split:
- git push is universal — works against any host without adaptation.
- The forge-specific parts are only creating the repo via API and post-setup.
Proposed design:
1. Minimal adapter interface: createRepo(name, visibility) → clone URL. Abstract no more API than that (anti maintenance-pit).
2. Capability model: each adapter declares which post-setup it supports (topics, branch_protection, secrets…); the app degrades gracefully, no lowest common denominator.
3. Universal fallback — "bring your own remote": the user hand-creates an empty repo on their host and pastes the URL; the app only pushes. Covers 100% of git hosts with zero adapters — adapters are convenience, not requirement.
4. Authentication separate from login: GitHub OAuth identifies the user in the app; creating repos on other forges requires a per-host token (PAT), stored encrypted. Gitea and Forgejo share a nearly identical API (one adapter serves both, plus Codeberg).
Implementation order: BYOR + GitHub adapter in the MVP; Gitea/Forgejo and GitLab later, based on real usage.
5. Manifest draft v1 (to be dry-run-validated)¶
# template.yml — draft, NOT a spec
name: react-vite-ts
platform: web # catalog tags; the engine ignores them
framework: react
variables:
- key: project_name # the engine derives kebab, Pascal, snake...
type: string
identity:
- from: "template-app" # appears in package.json, index.html...
to: "{project_name.kebab}"
- from: "TemplateApp" # root component
to: "{project_name.pascal}"
features:
- key: router
files: ["src/routes/**"]
patches:
- file: package.json
op: add
path: /dependencies/react-router-dom
value: "^7.0.0"
verify:
image: node:22
run: npm ci && npm run build
Schema validation plan (§7): write this same schema for three dissimilar templates — KMP (identity = JVM package + deep paths), React (JSON everywhere), Python/FastAPI (snake_case, pyproject.toml) — and check none needs hacks. If one does, the schema is wrong, not the template.
6. Edge-case catalog (design checklist)¶
- Substring collisions in renaming:
template-appinsidetemplate-applicationor a README badge URL. → Word-boundary renaming or a per-file exclusion list. - Cross-casing collisions:
templateapp(flat) can appear accidentally in normal text. → Application order: longest strings first; review in dry-run. - CRLF vs LF (the author develops on Windows): normalize output to LF +
.gitattributesin templates; golden tests must be insensitive or normalize. - Execute permissions (
gradlew, scripts): tar preserves them, zip not always; they don't exist on Windows — declare executables in the manifest or preserve them from GitHub's tarball. - Binary detection: heuristic (null bytes) + extension list; never scan directives in binaries.
- Empty directories: git doesn't track them — templates use
.gitkeep; the engine must not break them. - BOM and encodings: assume UTF-8; detect BOM and preserve it or fail with a clear error.
- Deep paths when renaming packages (
src/main/kotlin/com/template/base/…): segment-by-segment directory renaming; beware the 260-char Windows path limit. - Lockfiles (
package-lock.json,gradle.lockfile): contain the project name → either regenerated in verify, or part of the identity map. - The tarball's
.git: doesn't exist (tarball advantage over clone) — the new repo is born clean. - Mutually exclusive or dependent features:
requires/conflictsin the manifest? Candidate for v1.1 — don't complicate v1.
7. Validation plan (no production code)¶
- Dry manifest validation (D6 not needed): hand-write the 3 manifests of §5. Criterion: zero hacks.
- Golden tests (once a prototype exists): fixed inputs → byte-exact expected output, versioned in the engine repo.
- Per-template CI: the template compiles itself on every push + compiles the rendered output of the main feature combinations (matrix).
- Dry-run as a product: the readable operation plan is both a UX feature and an engine debugging tool.
8. Risks¶
| Risk | Impact | Mitigation |
|---|---|---|
| The manifest schema falls short and must break | High (it's the public API) | Dry validation (§7.1); schema_version field from v1 |
| Identity renaming produces false positives | Medium | Edge cases §6; dry-run with diff; golden tests |
| Scope creep toward Backstage | High | §1 scope; the web is phase 2; team/marketplace features out |
| Three-way-merge updates never arrive and generated projects are orphaned | Medium | .templetry-answers.yml from day 1 keeps the door open |
| Slow verify (Android builds take minutes) | Low | Verify optional/async interactively; mandatory only in CI |
| N forge adapters devour maintenance | Medium | Minimal interface (createRepo only) + capability model + BYOR fallback; new adapters only on real usage |
9. Proposed roadmap¶
- Phase 0 — Study (this document) → close D6 (language), D7 (grammar); dry-validate the manifest (3 templates).
- Phase 1 — Engine: library + CLI (
render,plan); golden tests; migrate the existing Android or KMP template to the format. - Phase 2 — Verify + CI: containers; templates with CI matrix; second and third real templates.
- Phase 3 — Web MVP: GitHub OAuth, catalog from
registry.json, dynamic form from the manifest, create repo + push (GitHub adapter + BYOR fallback). Deploy on the VPS (Docker). - Phase 4+ (optional): GitLab and Gitea/Forgejo adapters, three-way-merge updates, in-project generators, third-party templates.
10. Open questions to close Phase 0¶
- Engine language (D6) — needed before the prototype.
- Minimal directive grammar (D7) — just
var+if/endif? - ~~Project name (D8)~~ — resolved: Templetry (see D8).
- Do v1 features support dependencies between each other (
requires), or is that postponed to v1.1? - Does the manifest declare executables explicitly, or trust the tarball?