Skip to content

refactor(starters): render the starter workspaces from Go instead of shell - #615

Merged
viniciusdc merged 3 commits into
mainfrom
feat/starters-go-generator
Aug 25, 2026
Merged

refactor(starters): render the starter workspaces from Go instead of shell#615
viniciusdc merged 3 commits into
mainfrom
feat/starters-go-generator

Conversation

@viniciusdc

@viniciusdc viniciusdc commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Follows up @marcelovilla's review comment on #594: render the starters from a small Go command rather than a bash script, so the fragile parts are unit-testable.

What

scripts/gen-starters.sh becomes cmd/starters. Output is byte-identical - I diffed the two generators' full output trees before deleting the script.

The difference is what can go wrong, and when it is caught.

Why it is less fragile, concretely

The script located values by line prefix, indentation included:

'    email: '
'    url: '
'    path: '

That cannot tell the GitOps path: from any other four-space path: key, which is why the script grew a guard requiring each prefix to match exactly one line - and a restructure of examples/ only surfaced once CI had built nic and rendered a starter.

Resolving each field as a YAML path removes the ambiguity instead of defending against it:

"$.repository.existing.path"

A path either resolves or it fails naming itself. Three pieces of machinery go with it: the exactly-one-match guard, the sed dance that reattached a trailing comment to a replaced line, and the hardcoded indentation.

Comments and blank lines still survive

That was the one argument for staying in shell, and @marcelovilla checked it before raising this. The edit is applied to the source text rather than by marshalling the parsed document back out: each value's token gives the line and column where the value starts, so rewriting from that column leaves the rest of the line - trailing comment included - untouched.

    path: CHANGEME  # Optional subdirectory within the repository

goccy/go-yaml is already the parser on both sides of this - #603 made it the config parser, and #583's placeholder gate walks its AST - so this is the same library the validation half already uses, not a new dependency.

Block scalars are rejected explicitly: their token is the |/> indicator rather than the body, so a newline check never fires and an in-place edit would leave the following lines orphaned. That one cost me a red test before I understood it.

Tests

The point of the move. TestDeclaredFieldsResolveAgainstTheRealExamples resolves every declared field against the actual examples/*.yaml at go test time, so a renamed key fails on a laptop instead of in CI. TestPlaceholderConfigTargetsTheRightKeyAmongSameNamedSiblings pins the exact case the line-prefix match got wrong. Plus comment/blank-line preservation, the unresolvable-path error, block-scalar rejection, and the template renderer including its leftover-token check.

Test plan

  • go build ./..., go vet ./..., gofmt clean
  • go test -short ./... passes
  • Output is byte-identical to scripts/gen-starters.sh (diff -r over both trees, same -version)
  • The validate-starters assertions pass against the Go-rendered starters: rejected unedited, rejection comes from the placeholder gate, and it names exactly the expected field paths for both providers
  • make starters renders both providers; no leftover __TOKEN__; both pixi.toml parse

marcelovilla
marcelovilla previously approved these changes Aug 25, 2026

@marcelovilla marcelovilla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all the comments @viniciusdc! Approving now

This comment was actually meant for #594

@marcelovilla
marcelovilla self-requested a review August 25, 2026 16:51
@marcelovilla
marcelovilla dismissed their stale review August 25, 2026 16:53

Dismissing my own review as it was meant for another PR

Base automatically changed from feat/starter-local-quay-560 to main August 25, 2026 16:54
…shell

Replaces scripts/gen-starters.sh with cmd/starters. Output is byte-identical
to the shell version; the difference is what can go wrong and when it is
caught.

The shell script located values by line prefix, indentation included:

    '    email: '
    '    url: '
    '    path: '

That cannot distinguish the GitOps path: from any other four-space path: key,
so the script needed a guard requiring each prefix to match exactly one line,
and a restructure of examples/ was only caught once CI had built nic and
rendered a starter. Resolving each field as a YAML path instead
($.repository.existing.path) removes the ambiguity rather than defending
against it: a path either resolves or it fails, naming itself.

Three pieces of machinery go away with it - the exactly-one-match guard, the
sed dance that reattached a trailing comment to a replaced line, and the
hardcoded indentation.

Comments and blank lines still survive, which was the one argument for staying
in shell. The edit is applied to the source text rather than by marshalling the
parsed document back out: each value's token gives the line and column where
the value starts, so rewriting from that column leaves the rest of the line -
including a trailing comment - untouched. goccy/go-yaml is already the parser
on both sides of this: #603 made it the config parser and #583's placeholder
gate walks its AST.

Block scalars are rejected explicitly. Their token is the |/> indicator rather
than the body, so a newline check never fires and an in-place edit would leave
the following lines orphaned.

The tests are the point of the move: declared fields are resolved against the
real examples/*.yaml at go test time, so a renamed key fails on a laptop
instead of in CI, and the same-named-sibling case that the line-prefix match
got wrong is pinned directly.
@viniciusdc
viniciusdc force-pushed the feat/starters-go-generator branch from 0aaa205 to e665a0b Compare August 25, 2026 16:58
Comment thread .github/workflows/starters.yml Outdated
viniciusdc and others added 2 commits August 25, 2026 14:02
The three os.ReadFile calls take composed paths, which trips G304. Wrapped in
filepath.Clean, matching how cmd/docgen handles the same rule on its writes.
The test's read of a checked-in example is a fixed repo-relative path, so it
carries an annotated nolint instead, as cmd/docgen/cli_test.go does.

@marcelovilla marcelovilla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR @viniciusdc! As I mentioned before, I think this approach is more robust. Tested locally and works as expected.

@viniciusdc
viniciusdc merged commit 0147de4 into main Aug 25, 2026
8 checks passed
@viniciusdc
viniciusdc deleted the feat/starters-go-generator branch August 25, 2026 18:00
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.

3 participants