Skip to content

Commit dc2f053

Browse files
authored
refactor(scripts/typegen): use guts/zod serializer via local package (#38)
Move the Zod AST serializer from the deleted zod.go into a self-contained typegen/zod package, sourced from coder/guts#85 (feat/zod-mutation). The package provides AsSchemas (AST mutation) and SortByDependencies (topological ordering), replacing both the old string-builder serializer and the local topoSort. main.go keeps resolveTransitive and collectRefs for filtering to the wanted type subset. guts v1.7.0 -> v1.7.1 (walk changes from coder/guts#86).
1 parent b3fc81d commit dc2f053

10 files changed

Lines changed: 1332 additions & 768 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ all: gen build fmt lint test
66

77
gen: src/codersdk.gen.ts
88

9-
src/codersdk.gen.ts: scripts/typegen/main.go scripts/typegen/zod.go scripts/typegen/go.mod
9+
src/codersdk.gen.ts: scripts/typegen/main.go scripts/typegen/zod/zod.go scripts/typegen/go.mod
1010
@cd scripts/typegen && go run . > ../../src/codersdk.gen.ts.tmp
1111
@mv src/codersdk.gen.ts.tmp src/codersdk.gen.ts
1212
@bun run format -- src/codersdk.gen.ts || true

dist/index.js

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/typegen/go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ require (
1111
github.com/buger/jsonparser v1.1.2 // indirect
1212
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1313
github.com/coder/coder/v2 v2.34.0-rc.0.0.20260528065010-cfa343e45666 // indirect
14-
github.com/coder/guts v1.7.0 // indirect
14+
github.com/coder/guts v1.7.1 // indirect
1515
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 // indirect
1616
github.com/coder/serpent v0.15.0 // indirect
1717
github.com/coder/websocket v1.8.14 // indirect
1818
github.com/coreos/go-oidc/v3 v3.18.0 // indirect
19+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1920
github.com/dlclark/regexp2 v1.12.0 // indirect
2021
github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd // indirect
2122
github.com/fatih/structtag v1.2.0 // indirect
@@ -36,9 +37,11 @@ require (
3637
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
3738
github.com/pion/transport/v2 v2.2.10 // indirect
3839
github.com/pion/udp v0.1.4 // indirect
40+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3941
github.com/rivo/uniseg v0.4.7 // indirect
4042
github.com/shopspring/decimal v1.4.0 // indirect
4143
github.com/spf13/pflag v1.0.10 // indirect
44+
github.com/stretchr/testify v1.11.1 // indirect
4245
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
4346
github.com/zclconf/go-cty v1.18.1 // indirect
4447
go.opentelemetry.io/auto/sdk v1.2.1 // indirect

scripts/typegen/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ github.com/coder/coder/v2 v2.34.0-rc.0.0.20260528065010-cfa343e45666 h1:imJX12Fi
1616
github.com/coder/coder/v2 v2.34.0-rc.0.0.20260528065010-cfa343e45666/go.mod h1:5UG9p30Gqu3UoUz0fo18JzD5DBQj2S9ArDtbI8yhIr8=
1717
github.com/coder/guts v1.7.0 h1:TaZ/PR9wgN8dlbcckaWV1MxkkuEFZRwSRwBBEm8dYXs=
1818
github.com/coder/guts v1.7.0/go.mod h1:30SShdvpmsauNlsNjECRB5AppScjYk08rf2ZVpH3MFg=
19+
github.com/coder/guts v1.7.1 h1:NrxGxV1TeyOW7+1vBMZjJI1pYHTvlqWEh66GOZ3lsHI=
20+
github.com/coder/guts v1.7.1/go.mod h1:30SShdvpmsauNlsNjECRB5AppScjYk08rf2ZVpH3MFg=
1921
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
2022
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
2123
github.com/coder/serpent v0.15.0 h1:jobR7DnPsxzEMD0cRiailwlY+4v6HAPS/8emIgBpaIU=
@@ -26,6 +28,8 @@ github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d4
2628
github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
2729
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2830
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
31+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
32+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2933
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
3034
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
3135
github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd h1:QMSNEh9uQkDjyPwu/J541GgSH+4hw+0skJDIj9HJ3mE=
@@ -72,6 +76,8 @@ github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs
7276
github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8=
7377
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
7478
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
79+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
80+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7581
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
7682
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
7783
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -85,6 +91,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
8591
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
8692
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
8793
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
94+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
95+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
8896
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
8997
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
9098
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=

scripts/typegen/main.go

Lines changed: 41 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ package main
99
import (
1010
"fmt"
1111
"log"
12-
"maps"
1312
"os"
14-
"slices"
1513
"strings"
1614

1715
"github.com/coder/guts"
1816
"github.com/coder/guts/bindings"
17+
"github.com/coder/guts/bindings/walk"
1918
"github.com/coder/guts/config"
19+
20+
"github.com/coder/agents-chat-action/scripts/typegen/zod"
2021
)
2122

2223
// Types the action needs from the Coder API. Only root types that
@@ -63,38 +64,47 @@ func main() {
6364
log.Fatalf("to typescript: %v", err)
6465
}
6566

67+
// Pre-Zod mutations that reshape enums and optional fields.
6668
ts.ApplyMutations(
6769
config.EnumAsTypes,
6870
config.SimplifyOmitEmpty,
6971
)
7072

71-
// Collect all nodes then filter to wanted types plus any
72-
// types they reference that are also in the full set.
73+
// Compute wanted types before Zod rewrites the AST
74+
// (collectRefs works on Interface/Alias).
7375
allNodes := make(map[string]bindings.Node)
7476
ts.ForEach(func(name string, node bindings.Node) {
7577
allNodes[name] = node
7678
})
77-
78-
// Resolve transitive references so we don't emit broken
79-
// schema references.
8079
included := resolveTransitive(allNodes, wantedTypes)
80+
wantedNames := make(map[string]bool, len(included))
81+
for name := range included {
82+
wantedNames[name] = true
83+
}
8184

82-
// Topological sort: emit dependencies before dependents.
83-
names := topoSort(included)
84-
85-
var b strings.Builder
86-
b.WriteString("// Code generated by 'make gen'. DO NOT EDIT.\n")
87-
b.WriteString("import { z } from \"zod\";\n\n")
85+
// Rewrite Interface/Alias into Zod schemas, then export.
86+
ts.ApplyMutations(
87+
zod.AsSchemas,
88+
config.ExportTypes,
89+
)
8890

89-
for _, name := range names {
90-
s := serializeNode(name, included[name])
91-
if s != "" {
92-
b.WriteString(s)
93-
b.WriteString("\n")
91+
// Serialize only the wanted types, sorted by dependencies.
92+
output, err := ts.SerializeInOrder(func(nodes map[string]bindings.Node) []bindings.Node {
93+
// Filter to wanted types and their schema bindings.
94+
filtered := make(map[string]bindings.Node, len(wantedNames)*2)
95+
for name, node := range nodes {
96+
baseName := strings.TrimSuffix(name, "Schema")
97+
if wantedNames[baseName] {
98+
filtered[name] = node
99+
}
94100
}
101+
return zod.SortByDependencies(filtered)
102+
})
103+
if err != nil {
104+
log.Fatalf("serialize: %v", err)
95105
}
96106

97-
_, _ = fmt.Fprint(os.Stdout, b.String())
107+
_, _ = fmt.Fprint(os.Stdout, output)
98108
}
99109

100110
// resolveTransitive starts from the seeds and pulls in any
@@ -121,95 +131,21 @@ func resolveTransitive(allNodes map[string]bindings.Node, seeds map[string]bool)
121131
return result
122132
}
123133

124-
// collectRefs extracts all type reference names from a node.
134+
// collectRefs extracts all type reference names from a node
135+
// using the generic AST walker.
125136
func collectRefs(node bindings.Node) []string {
126-
var refs []string
127-
var walkExpr func(bindings.ExpressionType)
128-
walkExpr = func(expr bindings.ExpressionType) {
129-
if expr == nil {
130-
return
131-
}
132-
switch e := expr.(type) {
133-
case *bindings.ReferenceType:
134-
refs = append(refs, e.Name.Ref())
135-
for _, arg := range e.Arguments {
136-
walkExpr(arg)
137-
}
138-
case *bindings.ArrayType:
139-
walkExpr(e.Node)
140-
case *bindings.ArrayLiteralType:
141-
for _, el := range e.Elements {
142-
walkExpr(el)
143-
}
144-
case *bindings.UnionType:
145-
for _, t := range e.Types {
146-
walkExpr(t)
147-
}
148-
case *bindings.TypeLiteralNode:
149-
for _, m := range e.Members {
150-
walkExpr(m.Type)
151-
}
152-
case *bindings.TypeIntersection:
153-
for _, t := range e.Types {
154-
walkExpr(t)
155-
}
156-
case *bindings.ExpressionWithTypeArguments:
157-
walkExpr(e.Expression)
158-
for _, arg := range e.Arguments {
159-
walkExpr(arg)
160-
}
161-
case *bindings.OperatorNodeType:
162-
walkExpr(e.Type)
163-
case *bindings.TupleType:
164-
walkExpr(e.Node)
165-
}
166-
}
167-
switch n := node.(type) {
168-
case *bindings.Interface:
169-
for _, f := range n.Fields {
170-
walkExpr(f.Type)
171-
}
172-
for _, h := range n.Heritage {
173-
for _, arg := range h.Args {
174-
walkExpr(arg)
175-
}
176-
}
177-
case *bindings.Alias:
178-
walkExpr(n.Type)
179-
}
180-
return refs
137+
v := &refVisitor{}
138+
walk.Walk(v, node)
139+
return v.refs
181140
}
182141

183-
// topoSort returns names in dependency order (dependencies first).
184-
// Ties within the same depth level are broken alphabetically.
185-
func topoSort(nodes map[string]bindings.Node) []string {
186-
visited := make(map[string]bool)
187-
var order []string
188-
189-
var visit func(string)
190-
visit = func(name string) {
191-
if visited[name] {
192-
return
193-
}
194-
visited[name] = true
195-
node, ok := nodes[name]
196-
if !ok {
197-
return
198-
}
199-
// Visit dependencies first.
200-
deps := collectRefs(node)
201-
slices.Sort(deps)
202-
for _, dep := range deps {
203-
if _, exists := nodes[dep]; exists {
204-
visit(dep)
205-
}
206-
}
207-
order = append(order, name)
208-
}
142+
type refVisitor struct {
143+
refs []string
144+
}
209145

210-
// Start visits in alphabetical order for stable output.
211-
for _, name := range slices.Sorted(maps.Keys(nodes)) {
212-
visit(name)
146+
func (v *refVisitor) Visit(node bindings.Node) walk.Visitor {
147+
if ref, ok := node.(*bindings.ReferenceType); ok {
148+
v.refs = append(v.refs, ref.Name.Ref())
213149
}
214-
return order
150+
return v
215151
}

0 commit comments

Comments
 (0)