@@ -9,14 +9,15 @@ package main
99import (
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.
125136func 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