Jostraca code generation, made repeatable

Call Jostraca from Go

Drive the Go port from your own program, and know where its surface differs.

Rendered from docs/how-to/call-jostraca-from-go.md in the generator repository — where a correction belongs, and where the test suite runs every example on this page or states why it does not.

The Go port is the same generator with a Go-shaped surface. Components are methods on *J rather than free functions, and each callback receives a *J bound to the node it is inside—that shadowing is what replaces the ambient context the TypeScript components use.

package main

import (
	"fmt"

	jostraca "github.com/jostraca/jostraca/go"
)

func main() {
	j := jostraca.New(
		jostraca.WithFolder("./out"),
		jostraca.WithModel(map[string]any{
			"app": map[string]any{"name": "acme"},
		}),
	)

	res, err := j.Generate(jostraca.Options{}, func(j *jostraca.J) {
		j.Project(jostraca.ProjectProps{Folder: "acme"}, func(j *jostraca.J) {
			j.File("package.json", func(j *jostraca.J) {
				j.Content("{ \"name\": \"$$app.name$$\" }\n")
			})
		})
	})
	if err != nil {
		return
	}
	fmt.Println(res.Files.Written)
}

Three differences to expect coming from TypeScript:

  • Errors are returned, not thrown. Generate gives you (Result, error). Component methods short-circuit once an error is set, so a failing tree stops rather than compounding.
  • Options are a struct plus functional options. New(WithFolder(…)) for globals, an Options value for the call. OptionsFromMap builds one from decoded JSON or YAML.
  • Three flags are inverted so that Go’s zero value matches the TypeScript default: EachSpec.Raw, ListProps.NoLine and Control.NoDuplicate.

One option does not do what its name promises: a per-call Cmp is dropped by the option merge, so cmp.Copy.ignore has to be set on New. It is in the Go reference.

WithMem() and WithVol() work as their names suggest—Mem switches an in-memory filesystem on, Vol seeds it, and the result carries Vol and FS. They were inert before v0.35.0, writing real files while returning nil for both handles.

Concurrent Generate calls are isolated—the builder state hangs off the *J the callback receives rather than off a process-global—so two generates can run at once without seeing each other’s trees. That is one place the Go design is plainly better than the TypeScript one.

Output is byte-identical for the same logical input, held there by a shared test corpus both stacks read.

See also#