Repository navigation
feat(render): add Jinja template engine for Python and TypeScript - #504
Merged
Merged
Conversation
…check (#483) A template.prompt or template.output body may now be a Jinja file (.jinja / .j2 / .jinja2) instead of a .mustache one. The engine comes from the file or an explicit `engine` option, never from the metadata, so @textref and the metamodel stay language-neutral (ADR-0061). - Python renders with jinja2 (optional `jinja` extra); TypeScript with nunjucks (BSD-2; has include/import/extends, a walkable AST and an escaping hook). - verify and the render-helper generator walk the parsed template and check every payload reference: ERR_VAR_NOT_ON_PAYLOAD as before, plus ERR_TEMPLATE_NOT_STATIC for what cannot be settled statically and ERR_TEMPLATE_SYNTAX for a body that does not parse. A generated helper over a Jinja body passes the engine and field tree to render, so the check also runs at runtime. - New shared corpora: fixtures/jinja-render-conformance (12) and fixtures/jinja-verify-conformance (30), identical in both ports. - Java, Kotlin and C# render Mustache only and now refuse a Jinja-only body with ERR_TEMPLATE_ENGINE_UNSUPPORTED, in their providers and in verify. - Mustache stays the default; generated Mustache helpers are byte-identical; metamodelVersion does not move. Docs: ADR-0061, templates-and-payloads, the prompts skill references, port pages, llms.txt / llms-full.txt, CONFORMANCE (33 corpora), CHANGELOG.
# Conflicts: # AGENTS.md # docs/CONFORMANCE.md
…reads only .mustache Pre-gate simplifier pass: one MAX_DEPTH constant per port, the Jinja renderer reuses the Mustache renderer's scalar printer, and verify names the engine by its constant. Fix found in review: the TypeScript FilesystemProvider used the default extension as its 'not given' sentinel, so an explicit ".mustache" still fell back to Jinja files, against its own doc comment and the Python provider. It now falls back only when no extension is passed. Regression test in both ports.
# Conflicts: # CHANGELOG.md # bun.lock
The reference header listed a 'prismaChecksFileName' option. That is the helper that derives the checks file's name from 'fileName', not an option of PrismaSchemaFileOptions. The header now names the options the type declares: filter, target, fileName, checks, provider and header.
…ilures From the pre-gate code review (each reproduced first): - Escaping. Python escaped a macro's or call block's output a second time, and both ports did so for a block `set`. Rendered blocks now print as already escaped (Python: a marked concat; TypeScript: the capture is compiled as a safe string). New corpus case jinja-render-escaping-blocks. - One engine per include chain. A Mustache partial whose file is Jinja was spliced in as text, and a Jinja include of a Mustache file was read as Jinja. Both are now refused with ERR_TEMPLATE_ENGINE_UNSUPPORTED in verify and at render time, in TypeScript and Python. - Java and C#: a Mustache partial that exists only as a Jinja file crashed verify with the provider's exception; it is now that finding. - Python: a chained lookup on a missing key renders empty (as nunjucks), any jinja2 failure is a RenderError, and a dataclass or plain-object payload is read by its fields instead of being replaced with an empty context. - TypeScript: an unknown explicit `engine` is refused instead of falling back to Mustache, matching Python. - Verify: positional attribute arguments of sum, min, max and unique are checked, as the keyword form already was. New corpus case jinja-verify-filter-attribute-positional. The two remaining engine differences (looping over null; a string filter over already-escaped text) are listed in ADR-0061 and the templates page. Corpus counts are now 13 render and 31 verify cases.
… Fixed three stale spots: the Python render line in templates-and-payloads.md, the verify --templates row in cli.md, and a Jinja pointer in spec/conformance-tests.md. Nothing unresolved.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
jinja2, optionaljinjaextra) and TypeScript (nunjucks) can now render and verify Jinja templates. The engine is picked per port from the template file extension (.jinja/.j2/.jinja2) or an explicitengineoption, never from metadata, sometamodelVersiondoes not move (new ADR-0061). The build-time payload check (ERR_VAR_NOT_ON_PAYLOAD) now covers Jinja inverify, in the render-helper generator, and at runtime. Mustache remains the default and its generated output is unchanged.fixtures/jinja-render-conformance/(15 cases) andfixtures/jinja-verify-conformance/(31 cases), hold both ports to the same output and findings. The shared-corpus count is now 34.ERR_TEMPLATE_ENGINE_UNSUPPORTEDcode, inverifyand at render time.ERR_TEMPLATE_ENGINE_UNSUPPORTED(added toERROR-CODES.json), including a Mustache partial that exists only as a Jinja file.CHANGELOG.mdhas an Unreleased entry with no version.codegen-ts/src/reference/prisma-schema.ts, which named aprismaChecksFileNameoption thatPrismaSchemaFileOptionsdoes not have.Closes #483
Out of scope, by decision: Jinja in Java/Kotlin/C#, and the Jinja-to-Mustache migration analyzer.
Follow-ups outside this repo
llms.txtthe site serves should be refreshed fromdocs/llms/.Notes for the reviewer
no-mistakes(...)commits pluschore(site). The test step was approved as an exception: the configured test command passes, but its live-validation verdict is inconclusive because a library render tier has no product to drive./code-review highreturned 10 findings. 8 were fixed infix(render): Jinja review fixes. 2 were not changed: the root entry imports nunjucks, which resolves to its browser build via itsbrowserfield; andProviderChain.engineOfreads twice, because a shortcut would trust providers that report an engine for refs they don't hold.docs/CONFORMANCE.md. If feat(cli): install shared-model dependencies from npm and Python packages in meta deps sync #501 merges first, the resolution is docs-only.ts-fast,ts-unitandgateslanes.Validation
Risk Assessment
✅ Low: Fix round correctly adds format-aware escape/e/forceescape filters in Python matching nunjucks semantics, uses the matched extension for engine_of, removes unused jinja_reference_line, and strengthens the restore test; counts in docs match 15 fixtures.
Testing
scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains) passes on the final head. Its first run failed only on stale agent-context-conformance goldens, and those are regenerated and committed.| e,| escape,| forceescape) in html and json formats were run in both ports and match the new corpus cases. The Python symlink engine cases were run too.Pipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → fixed ✅
server/python/src/metaobjects/render/jinja_render.py:562- Python's| escape/| efilter skips the request's format escaper and diverges from TypeScript without erroring.render_jinjaturns jinja2 autoescape off and routes escaping throughfinalize. jinja2's built-inescapefilter still returns a markupsafeMarkup, andfinalizepasses that through untouched. Probe output,{{ t | e }}with t='a"b<': html format gives Pythona&#34;b&lt;and TSa&quot;b&lt;; text format gives Pythona&#34;b&lt;and TSa"b<. With t='a"b\n' in json format, Python givesa&#34;bfollowed by a raw newline, which is not a valid JSON string; TS givesa\"b\n. TS is right because nunjucks' escape filter calls the swappedlib.escape.| eis a common idiom in existing Jinja templates, so this breaks the docs' claim that every{{ … }}value goes through the format's escaper (docs/features/templates-and-payloads.md Escaping bullet, ADR-0061) and the claim that the two ports render alike. Fix: registerescape,eandforceescapefilters on the Environment that applyescapers.escape(format_, _stringify(v))and returnMarkup, leaving Markup/_Rendered values as they are. Add a jinja-render-conformance case using| ein html and json formats so both ports are held to it.server/python/src/metaobjects/render/filesystem_provider.py:419-FilesystemProvider.engine_oftakes the engine fromfound.suffix, andfoundis the symlink-RESOLVED path. The engine therefore follows the symlink target's extension, not the extension that was matched. Example:prompts/welcome.jinja -> ../shared/welcome.txtis read as Jinja text but reported as Mustache. The reverse case, a.mustachelink to a.j2file, renders Mustache text as Jinja. The TypeScript FilesystemProvider and the CLI FileProvider both use the matched candidate extension, so the ports disagree. Fix: have_findreturn the matched extension next to the path, as the TSfind()does, and pass that extension toengine_for_extension.server/typescript/packages/render/test/jinja-engine.test.ts:96- The test "a render leaves the engine's own escape function as it found it" cannot fail if restoration breaks. Its second call is itself a Jinja render that installs its own escaper (identity fortext), so it returns "<" even whenrenderJinjaTextnever restoresnunjucks.lib.escapeorCompiler.prototype.compileCapture. The behavior under test is that the module-level nunjucks state is restored, so an adopter's own nunjucks environment in the same process is unaffected. The independent oracle is nunjucks' documented default HTML autoescape. Replace the second assertion with a plainnew nunjucks.Environment(null, { autoescape: true }).renderString("{{ a }}", { a: "<" })that must equal&lt;, or assertnunjucks.lib.escapeis the same function before and after. Either version rejects a missingfinallyrestore.server/python/src/metaobjects/render/jinja_verify.py:521- Simplification: the Pythonjinja_reference_line(and its helper_spelling_of) is exported in__all__and unit-tested, but no production code calls it. The Python CLI's_verify_templatesprintsref '<ref>' — CODE: pathwith no file:line location for Mustache or Jinja. The TS counterpart exists becausemeta verifyprints a file:line location; Python has no such consumer, and the intent requires nothing here. Recommend removing it and its test, or wiring it into the Python CLI's drift line if a location is wanted. The remedy is the author's choice.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - resolved (approved exception: live validation inconclusive)
fixtures/agent-context-conformancegoldens: this branch edited the prompts skill without regenerating them, which failed thets-unitlane. They were regenerated withserver/typescript/packages/sdk/scripts/regen-agent-context-conformance.tsand committed inno-mistakes(test): Regenerate stale agent-context-conformance prompt-skill goldens(11 files). The sdk suite then passed (380 pass, 0 fail), and the configured test command passes.bun testover the whole ofserver/typescriptreturned no result within about 50 minutes. Its cause was not identified, and the finding was declined: that run is not the configured gate command, the project already says to run the server suite per package, and every per-package run passes.Commands:
scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains(passes), per-packagebun testforserver/typescript/packages, sdk goldens regen thenbun test.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - 1 issue found → fixed
site payload is truegate failed becauseexamples/showcase/site-payload.jsonstill counted 32 shared corpora; the two new Jinja corpora make it 34. It was regenerated withbun run site:payloadand committed inchore(site): refresh site payload corpus count to 34. The gates lane passes on re-check.✅ **Push** - passed
✅ No issues found.