@@ -8,17 +8,27 @@ import (
88 "strings"
99)
1010
11- func GenerateDocsFiles (commands Commands ) (map [string ][]byte , error ) {
11+ // GenerateDocsFiles generates documentation files from parsed commands.
12+ // groupedNames specifies command names whose subcommands should each get their
13+ // own file in a subdirectory (e.g., passing "cloud" produces cloud/namespace.mdx,
14+ // cloud/user.mdx, etc. instead of a single cloud.mdx).
15+ func GenerateDocsFiles (commands Commands , groupedNames []string ) (map [string ][]byte , error ) {
1216 optionSetMap := make (map [string ]OptionSets )
1317 for i , optionSet := range commands .OptionSets {
1418 optionSetMap [optionSet .Name ] = commands .OptionSets [i ]
1519 }
1620
21+ groupedParents := make (map [string ]bool )
22+ for _ , name := range groupedNames {
23+ groupedParents [name ] = true
24+ }
25+
1726 w := & docWriter {
1827 fileMap : make (map [string ]* bytes.Buffer ),
1928 optionSetMap : optionSetMap ,
2029 allCommands : commands .CommandList ,
2130 globalFlagsMap : make (map [string ]map [string ]Option ),
31+ groupedParents : groupedParents ,
2232 }
2333
2434 // sorted ascending by full name of command (activity complete, batch list, etc)
@@ -45,13 +55,34 @@ type docWriter struct {
4555 optionSetMap map [string ]OptionSets
4656 optionsStack [][]Option
4757 globalFlagsMap map [string ]map [string ]Option // fileName -> optionName -> Option
58+ groupedParents map [string ]bool // depth-1 command names that use subdirectory splitting
4859}
4960
5061func (c * Command ) writeDoc (w * docWriter ) error {
5162 w .processOptions (c )
5263
53- // If this is a root command, write a new file
5464 depth := c .depth ()
65+
66+ // Check if this command itself is the grouped root, or belongs to one.
67+ // A grouped root command (e.g., "cloud") is skipped - it has no standalone file.
68+ // Its direct children (e.g., "cloud namespace") each get their own file in a subdirectory.
69+ if w .groupedParents [c .FullName ] {
70+ // This is the grouped root - skip it
71+ return nil
72+ }
73+ if depth >= 1 {
74+ // Walk up the tree to find if any ancestor is a grouped parent
75+ parts := strings .Split (c .FullName , " " )
76+ for i := len (parts ) - 1 ; i >= 1 ; i -- {
77+ ancestor := strings .Join (parts [:i ], " " )
78+ if w .groupedParents [ancestor ] {
79+ c .writeGroupedDoc (w , ancestor )
80+ return nil
81+ }
82+ }
83+ }
84+
85+ // Standard (non-grouped) handling
5586 if depth == 1 {
5687 w .writeCommand (c )
5788 } else if depth > 1 {
@@ -60,6 +91,22 @@ func (c *Command) writeDoc(w *docWriter) error {
6091 return nil
6192}
6293
94+ func (c * Command ) writeGroupedDoc (w * docWriter , groupRoot string ) {
95+ // How many parts does the group root have?
96+ groupDepth := len (strings .Split (groupRoot , " " ))
97+ parts := strings .Split (c .FullName , " " )
98+ relativeDepth := len (parts ) - groupDepth
99+
100+ switch {
101+ case relativeDepth == 1 :
102+ // Direct child of grouped root (e.g., "cloud namespace") - new file
103+ w .writeGroupedCommand (c , groupRoot )
104+ case relativeDepth > 1 :
105+ // Deeper subcommand (e.g., "cloud namespace get") - append to parent file
106+ w .writeGroupedSubcommand (c , groupRoot )
107+ }
108+ }
109+
63110func (w * docWriter ) writeCommand (c * Command ) {
64111 fileName := c .fileName ()
65112 w .fileMap [fileName ] = & bytes.Buffer {}
@@ -88,6 +135,107 @@ func (w *docWriter) writeCommand(c *Command) {
88135 w .fileMap [fileName ].WriteString ("Refer to [Global Flags](#global-flags) for flags that you can use with every subcommand.\n \n " )
89136}
90137
138+ // groupedFileName returns the file path for a command within a grouped parent.
139+ // For example, with groupRoot "cloud" and command "cloud namespace", returns "cloud/namespace".
140+ func groupedFileName (c * Command , groupRoot string ) string {
141+ groupParts := strings .Split (groupRoot , " " )
142+ cmdParts := strings .Split (c .FullName , " " )
143+ // The file is named after the first part after the group root
144+ if len (cmdParts ) <= len (groupParts ) {
145+ return ""
146+ }
147+ return strings .Join (groupParts , "-" ) + "/" + cmdParts [len (groupParts )]
148+ }
149+
150+ func (w * docWriter ) writeGroupedCommand (c * Command , groupRoot string ) {
151+ fileName := groupedFileName (c , groupRoot )
152+ groupParts := strings .Split (groupRoot , " " )
153+ cmdParts := strings .Split (c .FullName , " " )
154+ leafName := cmdParts [len (groupParts )]
155+ fullCmdName := strings .Join (cmdParts , " " )
156+
157+ w .fileMap [fileName ] = & bytes.Buffer {}
158+ w .fileMap [fileName ].WriteString ("---\n " )
159+ w .fileMap [fileName ].WriteString ("id: " + leafName + "\n " )
160+ w .fileMap [fileName ].WriteString ("title: Temporal CLI " + fullCmdName + " command reference\n " )
161+ w .fileMap [fileName ].WriteString ("sidebar_label: " + leafName + "\n " )
162+ w .fileMap [fileName ].WriteString ("description: " + c .Docs .DescriptionHeader + "\n " )
163+ w .fileMap [fileName ].WriteString ("toc_max_heading_level: 4\n " )
164+
165+ w .fileMap [fileName ].WriteString ("keywords:\n " )
166+ for _ , keyword := range c .Docs .Keywords {
167+ w .fileMap [fileName ].WriteString (" - " + keyword + "\n " )
168+ }
169+ w .fileMap [fileName ].WriteString ("tags:\n " )
170+ for _ , tag := range c .Docs .Tags {
171+ w .fileMap [fileName ].WriteString (" - " + tag + "\n " )
172+ }
173+ w .fileMap [fileName ].WriteString ("---" )
174+ w .fileMap [fileName ].WriteString ("\n \n " )
175+ w .fileMap [fileName ].WriteString ("{/* NOTE: This is an auto-generated file. Any edit to this file will be overwritten.\n " )
176+ w .fileMap [fileName ].WriteString ("This file is generated from https://github.com/temporalio/cli via cmd/gen-docs */}\n \n " )
177+ w .fileMap [fileName ].WriteString (fmt .Sprintf ("This page provides a reference for the `temporal %s` commands. " , fullCmdName ))
178+ w .fileMap [fileName ].WriteString ("The flags applicable to each subcommand are presented in a table within the heading for the subcommand. " )
179+ w .fileMap [fileName ].WriteString ("Refer to [Global Flags](#global-flags) for flags that you can use with every subcommand.\n \n " )
180+ }
181+
182+ func (w * docWriter ) writeGroupedSubcommand (c * Command , groupRoot string ) {
183+ fileName := groupedFileName (c , groupRoot )
184+ groupParts := strings .Split (groupRoot , " " )
185+ cmdParts := strings .Split (c .FullName , " " )
186+ // Heading depth is relative to the grouped file root
187+ // e.g., "cloud namespace get" with groupRoot "cloud" → relativeDepth 2 → ## heading
188+ relativeDepth := len (cmdParts ) - len (groupParts )
189+ prefix := strings .Repeat ("#" , relativeDepth )
190+ leafParts := cmdParts [len (groupParts )+ 1 :]
191+ leafName := strings .Join (leafParts , "" )
192+
193+ w .fileMap [fileName ].WriteString (prefix + " " + leafName + "\n \n " )
194+ w .fileMap [fileName ].WriteString (c .Description + "\n \n " )
195+
196+ if w .isLeafCommand (c ) {
197+ var options = make ([]Option , 0 )
198+ var globalOptions = make ([]Option , 0 )
199+ for i , o := range w .optionsStack {
200+ if i == len (w .optionsStack )- 1 {
201+ options = append (options , o ... )
202+ } else {
203+ globalOptions = append (globalOptions , o ... )
204+ }
205+ }
206+
207+ sort .Slice (options , func (i , j int ) bool {
208+ return options [i ].Name < options [j ].Name
209+ })
210+
211+ if len (options ) > 0 {
212+ w .fileMap [fileName ].WriteString ("Use the following options to change the behavior of this command. " )
213+ w .fileMap [fileName ].WriteString ("You can also use any of the [global flags](#global-flags) that apply to all subcommands.\n \n " )
214+ w .writeGroupedOptionsTable (options , fileName )
215+ } else {
216+ w .fileMap [fileName ].WriteString ("Use [global flags](#global-flags) to customize the connection to the Temporal Service for this command.\n \n " )
217+ }
218+
219+ w .collectGlobalFlags (fileName , globalOptions )
220+ }
221+ }
222+
223+ func (w * docWriter ) writeGroupedOptionsTable (options []Option , fileName string ) {
224+ if len (options ) == 0 {
225+ return
226+ }
227+
228+ buf := w .fileMap [fileName ]
229+
230+ buf .WriteString ("| Flag | Required | Description |\n " )
231+ buf .WriteString ("|------|----------|-------------|\n " )
232+
233+ for _ , o := range options {
234+ w .writeOptionRow (buf , o , false )
235+ }
236+ buf .WriteString ("\n " )
237+ }
238+
91239func (w * docWriter ) writeSubcommand (c * Command ) {
92240 fileName := c .fileName ()
93241 prefix := strings .Repeat ("#" , c .depth ())
@@ -255,10 +403,11 @@ func (w *docWriter) isLeafCommand(c *Command) bool {
255403}
256404
257405func encodeJSONExample (v string ) string {
258- // example: 'YourKey={"your": "value"}'
259- // results in an mdx acorn rendering error
260- // and wrapping in backticks lets it render
261- re := regexp .MustCompile (`('[a-zA-Z0-9]*={.*}')` )
406+ // JSON objects in single quotes cause MDX acorn rendering errors
407+ // because curly braces are interpreted as JSX expressions.
408+ // Wrapping in backticks makes them render as inline code.
409+ // Matches both 'Key={"value"}' and '{"key": "value"}' patterns.
410+ re := regexp .MustCompile (`('\{.*?\}')` )
262411 v = re .ReplaceAllString (v , "`$1`" )
263412 return v
264413}
0 commit comments