Skip to content

Commit 859c17f

Browse files
committed
refactor(scripts/typegen): extract zod serializer into typegen/zod package
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 ab86eb6 commit 859c17f

9 files changed

Lines changed: 1288 additions & 158 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/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.1-0.20260529230818-2f30faf483eb // 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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +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-0.20260529230818-2f30faf483eb h1:MjlXdlmJwVf24NGrdE+2/spUKJlgNeAmNAx7RtobwoI=
20-
github.com/coder/guts v1.7.1-0.20260529230818-2f30faf483eb/go.mod h1:VAC7GjXGIoM747tMmabVQHTzb/ZtAQxGaFCiZE9g/C4=
19+
github.com/coder/guts v1.7.1 h1:NrxGxV1TeyOW7+1vBMZjJI1pYHTvlqWEh66GOZ3lsHI=
20+
github.com/coder/guts v1.7.1/go.mod h1:30SShdvpmsauNlsNjECRB5AppScjYk08rf2ZVpH3MFg=
2121
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
2222
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
2323
github.com/coder/serpent v0.15.0 h1:jobR7DnPsxzEMD0cRiailwlY+4v6HAPS/8emIgBpaIU=
@@ -28,6 +28,8 @@ github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d4
2828
github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
2929
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3030
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=
3133
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
3234
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
3335
github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd h1:QMSNEh9uQkDjyPwu/J541GgSH+4hw+0skJDIj9HJ3mE=
@@ -74,6 +76,8 @@ github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs
7476
github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8=
7577
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
7678
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=
7781
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
7882
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
7983
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -87,6 +91,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
8791
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
8892
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
8993
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=
9096
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
9197
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
9298
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=

scripts/typegen/main.go

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ package main
99
import (
1010
"fmt"
1111
"log"
12-
"maps"
1312
"os"
14-
"slices"
13+
"strings"
1514

1615
"github.com/coder/guts"
1716
"github.com/coder/guts/bindings"
1817
"github.com/coder/guts/config"
19-
"github.com/coder/guts/zod"
18+
19+
"github.com/coder/agents-chat-action/scripts/typegen/zod"
2020
)
2121

2222
// Types the action needs from the Coder API. Only root types that
@@ -69,34 +69,35 @@ func main() {
6969
config.SimplifyOmitEmpty,
7070
)
7171

72-
// Compute wanted types and dependency order before Zod
73-
// rewrites the AST (collectRefs works on Interface/Alias).
72+
// Compute wanted types before Zod rewrites the AST
73+
// (collectRefs works on Interface/Alias).
7474
allNodes := make(map[string]bindings.Node)
7575
ts.ForEach(func(name string, node bindings.Node) {
7676
allNodes[name] = node
7777
})
7878
included := resolveTransitive(allNodes, wantedTypes)
79-
order := topoSort(included)
79+
wantedNames := make(map[string]bool, len(included))
80+
for name := range included {
81+
wantedNames[name] = true
82+
}
8083

8184
// Rewrite Interface/Alias into Zod schemas, then export.
8285
ts.ApplyMutations(
8386
zod.AsSchemas,
8487
config.ExportTypes,
8588
)
8689

90+
// Serialize only the wanted types, sorted by dependencies.
8791
output, err := ts.SerializeInOrder(func(nodes map[string]bindings.Node) []bindings.Node {
88-
var result []bindings.Node
89-
for _, name := range order {
90-
// AsSchemas creates FooSchema (VariableStatement) and
91-
// Foo (Alias with z.infer<typeof FooSchema>).
92-
if schema, ok := nodes[name+"Schema"]; ok {
93-
result = append(result, schema)
94-
}
95-
if typeNode, ok := nodes[name]; ok {
96-
result = append(result, typeNode)
92+
// Filter to wanted types and their schema bindings.
93+
filtered := make(map[string]bindings.Node, len(wantedNames)*2)
94+
for name, node := range nodes {
95+
baseName := strings.TrimSuffix(name, "Schema")
96+
if wantedNames[baseName] {
97+
filtered[name] = node
9798
}
9899
}
99-
return result
100+
return zod.SortByDependencies(filtered)
100101
})
101102
if err != nil {
102103
log.Fatalf("serialize: %v", err)
@@ -187,37 +188,3 @@ func collectRefs(node bindings.Node) []string {
187188
}
188189
return refs
189190
}
190-
191-
// topoSort returns names in dependency order (dependencies first).
192-
// Ties within the same depth level are broken alphabetically.
193-
func topoSort(nodes map[string]bindings.Node) []string {
194-
visited := make(map[string]bool)
195-
var order []string
196-
197-
var visit func(string)
198-
visit = func(name string) {
199-
if visited[name] {
200-
return
201-
}
202-
visited[name] = true
203-
node, ok := nodes[name]
204-
if !ok {
205-
return
206-
}
207-
// Visit dependencies first.
208-
deps := collectRefs(node)
209-
slices.Sort(deps)
210-
for _, dep := range deps {
211-
if _, exists := nodes[dep]; exists {
212-
visit(dep)
213-
}
214-
}
215-
order = append(order, name)
216-
}
217-
218-
// Start visits in alphabetical order for stable output.
219-
for _, name := range slices.Sorted(maps.Keys(nodes)) {
220-
visit(name)
221-
}
222-
return order
223-
}

scripts/typegen/main_test.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"slices"
54
"testing"
65

76
"github.com/coder/guts/bindings"
@@ -65,57 +64,6 @@ func TestResolveTransitiveStopsAtMissingRefs(t *testing.T) {
6564
}
6665
}
6766

68-
func TestTopoSort(t *testing.T) {
69-
t.Parallel()
70-
71-
nodes := map[string]bindings.Node{
72-
"C": &bindings.Interface{
73-
Name: bindings.Identifier{Name: "C"},
74-
Fields: []*bindings.PropertySignature{{Name: "x", Type: kw(bindings.KeywordString)}},
75-
},
76-
"B": &bindings.Interface{
77-
Name: bindings.Identifier{Name: "B"},
78-
Fields: []*bindings.PropertySignature{{Name: "c", Type: ref("C")}},
79-
},
80-
"A": &bindings.Interface{
81-
Name: bindings.Identifier{Name: "A"},
82-
Fields: []*bindings.PropertySignature{{Name: "b", Type: ref("B")}},
83-
},
84-
}
85-
86-
order := topoSort(nodes)
87-
88-
indexOf := func(name string) int {
89-
return slices.Index(order, name)
90-
}
91-
92-
if indexOf("C") > indexOf("B") {
93-
t.Error("C should appear before B (B depends on C)")
94-
}
95-
if indexOf("B") > indexOf("A") {
96-
t.Error("B should appear before A (A depends on B)")
97-
}
98-
}
99-
100-
func TestTopoSortSelfReference(t *testing.T) {
101-
t.Parallel()
102-
103-
nodes := map[string]bindings.Node{
104-
"Tree": &bindings.Interface{
105-
Name: bindings.Identifier{Name: "Tree"},
106-
Fields: []*bindings.PropertySignature{
107-
{Name: "children", Type: bindings.Array(ref("Tree"))},
108-
},
109-
},
110-
}
111-
112-
order := topoSort(nodes)
113-
114-
if len(order) != 1 || order[0] != "Tree" {
115-
t.Errorf("expected [Tree], got %v", order)
116-
}
117-
}
118-
11967
func TestCollectRefsFindsNestedRefs(t *testing.T) {
12068
t.Parallel()
12169

0 commit comments

Comments
 (0)