Skip to content

feat(manifest): add template directives, indent-aware substitution and comma join - #28

Merged
kboshold merged 32 commits into
mainfrom
feat/template-directives
Oct 20, 2026
Merged

kboshold merged 32 commits into
mainfrom
feat/template-directives

Conversation

@kboshold

@kboshold kboshold commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

What

  • Template directives (manifest schemaVersion 5). Templates and fragments can wrap lines in comment directives written in the file's own comment syntax, so the template stays valid YAML, Dockerfile or Markdown:

    steps:
      # streamctl:if features.prisma
      - run: pnpm prisma generate
      # streamctl:else features.prisma
      - run: echo "no database"
      # streamctl:end features.prisma

    The branch that doesn't apply and all directive lines are removed before fragments and placeholders are rendered. A condition is a config path declared in a preset's configKeys. true, a non-empty string or a non-empty array count as true; unset, false, "" and [] count as false. Directives also work on files without a render block.

  • Errors surface when the preset chain resolves, before anything is written, and name the file and line (presets/<source>:<line>: …). This covers broken nesting, mismatched end/else names, YAML bodies indented less than their directive, undeclared or object-valued conditions, directives in JSON or merge files, and directives in payloads below schemaVersion 5 (where other streamctl: comments stay plain content). Every template is checked, including files switched off or disabled by enabledBy.

  • Indent-aware substitution. Extra lines of a multi-line placeholder value get the indentation of the line holding the token, so run: | blocks stay valid. Tokens at column 0 render exactly as before.

  • Placeholders are only resolved when their token is reachable from the rendered text, directly or through another value. A token inside a dropped block, or a declared placeholder nobody uses, no longer fails the render through its pattern.

  • join: "comma" (schema 5) renders string[] placeholders as dev, main. It skips the shell-metacharacter check that space applies, since the value isn't going into a shell; pattern is the guard.

  • init runs its first sync with the scaffolded streamctl.config.ts instead of a bare { package, base, version, profile }, so payload wrapper defaults apply from the start. If the file can't be loaded, it warns once and falls back to the old behavior.

  • scanDirectives, commentSyntaxFor, DIRECTIVES_MIN_SCHEMA_VERSION and COMMA_JOIN_MIN_SCHEMA_VERSION are exported from @sidebase/streamctl/manifest, so payloads can lint their templates in their own CI.

v2 to v4 payloads parse unchanged and render the same, except that multi-line placeholder values on indented lines are now indented (see release notes). Docs are in the README, docs/reference.md and the release notes in docs/release.md.

Why

Payloads needed per-consumer variation inside a single file: a CI step only some repos want, a Dockerfile stage behind a feature switch. The only options were enabledBy (whole file on or off) or fragments appended at the end, which can't put a step in the middle of a job. Comment directives give conditional blocks at any position without breaking the template's own syntax, so editors, linters and validateStructured keep working on the source.

Conditions are config paths only, not dependency or file checks. Version reconcile adds baseline pins to every consumer, so "has dependency X" ends up true everywhere. An explicit switch in the config, with its default set by the payload's config wrapper, is the reliable signal. That's also why init now has to load the scaffolded config: without it the first sync ignores those defaults.

Minor bump: new schema version, no removals.

@kboshold
kboshold merged commit e3e1b90 into main Oct 20, 2026
8 checks passed
@kboshold
kboshold deleted the feat/template-directives branch October 20, 2026 08:11
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