Skip to content

feat(schema): typed CLI properties for extends-based formae#545

Closed
naxty wants to merge 1 commit into
mainfrom
nicoaxtmann/rfc-105-extends-properties
Closed

feat(schema): typed CLI properties for extends-based formae#545
naxty wants to merge 1 commit into
mainfrom
nicoaxtmann/rfc-105-extends-properties

Conversation

@naxty

@naxty naxty commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Forma files can now extends "@formae/forma.pkl" and declare CLI properties as a plain typed class (properties: Props + class Props { name: String = "pel" }). Member name = flag, declared default = default, doc comment = help text, pkl type constraints validate CLI input (-p port=-5 fails the Int(this > 0) constraint).
  • Property access becomes statically typed: editors resolve properties.name instead of flagging it as an unresolved reference, which is the biggest source of red squiggles in forma files today.
  • Injection is self-contained in forma.pkl: the output amends the module once with a properties instance filled from external prop: values, then renders normally — late binding carries injected values into all resources. Same mechanism pkl's own pkl:Command runtime uses. Plain pkl eval -p name=test forma.pkl works unchanged; no evaluator/Go changes.
  • Backward compatible by construction: properties widens to Any?, legacy properties {} blocks pass through the render untouched, amends-based formae keep working against the now-open module.

Verification

  • make test-pkl and go test ./internal/schema/pkl/... green with the existing (unmigrated) testdata.
  • Serialized JSON of legacy formae is byte-identical to main.
  • New shape covered by testdata/forma/extends_props_test.pkl: defaults, -p injection into resources and manifest, constraint violations.
  • Stray module properties (e.g. Clouds = …) never reach the rendered output — only FormaRender serializes.

Deliberately not in this PR

  • Validation throw for unknown/typo'd module properties under extends (form {} currently vanishes silently — same follow-up as the design discussion).
  • Scaffold template + testdata migration to the new shape, docs.
  • Optional CLI enrichment (doc comments → --help, typed flags).

Forma files can now extend the base module and declare CLI properties as a
plain typed class:

    extends "@formae/forma.pkl"
    properties: Props
    class Props {
      /// The name to deploy
      name: String = "pel"
      port: Int(this > 0) = 8080
    }

Member name = flag, declared default = default, doc comment = help text, and
pkl type constraints validate CLI input. Property access is statically typed,
so editors resolve properties.name instead of flagging it as unresolved.

Mechanics: forma.pkl's output now self-injects - on first evaluation the
module amends itself with a properties instance filled from external prop:
values (reflect members, read, coerce by declared type, fall back to declared
defaults, validate via toTyped), then evaluates the amended module's output.
Late binding carries the injected values into all resources. This mirrors how
pkl:Command's runtime injects options (synthesize a module that amends the
command module). The manifest renders Value from the instance so manifest and
resources always agree.

Backward compatible: properties is widened to Any?, legacy properties{} blocks
pass through the render unchanged, amends-based formae keep working against
the now-open module, and their serialized JSON is byte-identical. Stray module
properties never render (output serializes FormaRender only).

Not included yet: unknown-property validation throw, scaffold/testdata
migration, docs.
@naxty

naxty commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #546 (same change, branch renamed).

@naxty naxty closed this Jul 13, 2026
@naxty naxty deleted the nicoaxtmann/rfc-105-extends-properties branch July 13, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant