Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions internal/cmddocs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# internal/cmddocs

Walks the declarative `cmd.Root` tree and emits a versioned JSON description of
every command, flag, example, output shape, and typed error. Consumed by
`docs.baseten.co` to generate the published CLI reference.

This is a developer tool, not a user-facing `baseten` subcommand. Its only
caller is the `docs.baseten.co` pipeline.

## Output contract

The JSON shape is defined by the Go types in `schema.go`. The top-level
`schema_version` field is "1"; bump `SchemaVersion` in `schema.go` whenever an
existing field is removed or its meaning changes (adding a new optional field
does **not** require a bump).

`group_pri` is emitted verbatim: `0` means the flag set no `group-pri`, and the
consumer applies the framework default (`DefaultFlagGroupPri = 100`). The walker
does not resolve it.

## Running locally

```sh
# Write to stdout (default).
go run ./internal/cmddocs --cli-version=dev

# Write to a file.
go run ./internal/cmddocs --cli-version=v0.1.0 --out=docs.json

# Reproducible timestamp.
SOURCE_DATE_EPOCH=1700000000 go run ./internal/cmddocs --cli-version=v0.1.0 --out=docs.json
```

## Tests

```sh
go test ./internal/cmddocs/...
```

Unit tests exercise the walker over synthetic command trees and assert the
emitted JSON is valid and stable. There is no committed snapshot to maintain:
schema drift surfaces in the `docs.baseten.co` pipeline, which regenerates the
reference against a pinned baseten-cli ref and opens a PR on any change.

## How `docs.baseten.co` consumes this

The docs pipeline pins a baseten-cli ref, checks it out, and runs
`go run ./internal/cmddocs --cli-version=<tag> --out=docs.json`, then feeds the
result to its MDX generator. Nothing in this repo's release flow runs the
emitter or publishes `docs.json`.
63 changes: 63 additions & 0 deletions internal/cmddocs/main.go

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this move to internal/cmddocs to be less user facing? Running go run ./internal/cmddocs should be ok (and changing the other things in the package to be package main should be harmless for this kind of tool)

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Command cmddocs walks the declarative cmd.Root tree and emits a versioned
// JSON description for consumption by docs.baseten.co. It is invoked by the
// docs.baseten.co pipeline against a pinned baseten-cli ref; nothing in this
// repo's release flow runs it.
//
// Usage:
//
// go run ./internal/cmddocs --cli-version=v0.1.0 --out=docs.json
// go run ./internal/cmddocs --cli-version=dev # writes to stdout
//
// Set SOURCE_DATE_EPOCH (Unix seconds) to pin GeneratedAt for reproducible
// output.
package main

import (
"encoding/json"
"flag"
"fmt"
"os"
"strconv"
"time"

cmdpkg "github.com/basetenlabs/baseten-cli/cmd"
)

func main() {
cliVersion := flag.String("cli-version", "dev", "CLI version string to embed in the output (e.g. v0.1.0).")
outPath := flag.String("out", "-", "Output file path; '-' writes to stdout.")
flag.Parse()

generatedAt := time.Now().UTC().Format(time.RFC3339)
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
secs, err := strconv.ParseInt(epoch, 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "invalid SOURCE_DATE_EPOCH %q: %v\n", epoch, err)
os.Exit(2)
}
generatedAt = time.Unix(secs, 0).UTC().Format(time.RFC3339)
}

schema := Walk(*cliVersion, generatedAt, cmdpkg.Root)
payload, err := json.MarshalIndent(schema, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "marshal: %v\n", err)
os.Exit(1)
}
payload = append(payload, '\n')

w := os.Stdout
if *outPath != "-" {
f, err := os.Create(*outPath)
if err != nil {
fmt.Fprintf(os.Stderr, "create %s: %v\n", *outPath, err)
os.Exit(1)
}
defer f.Close()
w = f
}
if _, err := w.Write(payload); err != nil {
fmt.Fprintf(os.Stderr, "write: %v\n", err)
os.Exit(1)
}
}
76 changes: 76 additions & 0 deletions internal/cmddocs/schema.go

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this package be called cmddocs or docgen or similar? As docs/ it looks like actual docs

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package main

// This file defines the versioned JSON schema emitted for docs.baseten.co. The
// schema is the contract; bump SchemaVersion on any breaking change and
// coordinate with the consumer.

// SchemaVersion is the major version of the emitted JSON. Increment when an
// existing field is removed or its semantics change. Adding a new optional
// field does not require a bump.
const SchemaVersion = "1"

// Schema is the top-level document emitted by the walker.
type Schema struct {
SchemaVersion string `json:"schema_version"`
CLIVersion string `json:"cli_version"`
// GeneratedAt is the UTC RFC3339 timestamp this Schema was emitted at.
GeneratedAt string `json:"generated_at"`
StandardErrors []ErrorEntry `json:"standard_errors"`
Root Command `json:"root"`
}

// Command is one node in the command tree. Non-leaf commands (with Children)
// have empty Flags/Examples/Output fields.
type Command struct {
Name string `json:"name"`
Path []string `json:"path"`
Summary string `json:"summary"`
Description string `json:"description"`
IsLeaf bool `json:"is_leaf"`
ArgsUsage string `json:"args_usage"`
ExactArgs int `json:"exact_args"`
MaxArgs int `json:"max_args"`
DisableFlagParsing bool `json:"disable_flag_parsing"`
Flags []Flag `json:"flags"`
Examples []Example `json:"examples"`
JQExample *Example `json:"jq_example"`
TextDescription string `json:"text_description"`
JSONDescription string `json:"json_description"`
JSONOutputType string `json:"json_output_type"`
JSONArrayStreamed bool `json:"json_array_streamed"`
Errors []ErrorEntry `json:"errors"`
Children []Command `json:"children"`
}

// Flag is one CLI flag on a leaf command.
type Flag struct {
Name string `json:"name"`
Short string `json:"short"`
Description string `json:"description"`
Default string `json:"default"`
Enum []string `json:"enum"`
Required bool `json:"required"`
Oneof string `json:"oneof"`
Type string `json:"type"`
FieldName string `json:"field_name"`
Group string `json:"group"`
// GroupPri is the rendering priority for the flag's group (lower renders
// earlier), copied verbatim from the source field's group-pri tag. A value
// of 0 means the field set no group-pri; the consumer applies the framework
// default (DefaultFlagGroupPri = 100). The walker does not resolve it.
GroupPri int `json:"group_pri"`
}

// Example is one documented invocation of a command.
type Example struct {
Description string `json:"description"`
Command string `json:"command"`
}

// ErrorEntry is one typed error a command may surface. Standard errors at the
// top level are inherited by every leaf; per-command Errors lists additions.
type ErrorEntry struct {
Name string `json:"name"`
Code int `json:"code"`
Meaning string `json:"meaning"`
}
54 changes: 54 additions & 0 deletions internal/cmddocs/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"encoding/json"
"testing"
)

func Test_Schema_VersionIsStable(t *testing.T) {
if SchemaVersion != "1" {
t.Fatalf("SchemaVersion = %q, want %q (bump deliberately and update docs.baseten.co consumer)", SchemaVersion, "1")
}
}

func Test_Schema_MarshalsEmpty(t *testing.T) {
s := Schema{
SchemaVersion: SchemaVersion,
CLIVersion: "v0.0.0-test",
GeneratedAt: "2026-01-01T00:00:00Z",
}
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
t.Fatalf("marshal: %v", err)
}
got := string(b)
want := `{
"schema_version": "1",
"cli_version": "v0.0.0-test",
"generated_at": "2026-01-01T00:00:00Z",
"standard_errors": null,
"root": {
"name": "",
"path": null,
"summary": "",
"description": "",
"is_leaf": false,
"args_usage": "",
"exact_args": 0,
"max_args": 0,
"disable_flag_parsing": false,
"flags": null,
"examples": null,
"jq_example": null,
"text_description": "",
"json_description": "",
"json_output_type": "",
"json_array_streamed": false,
"errors": null,
"children": null
}
}`
if got != want {
t.Fatalf("JSON mismatch.\nGot:\n%s\nWant:\n%s", got, want)
}
}
99 changes: 99 additions & 0 deletions internal/cmddocs/walk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
cmdpkg "github.com/basetenlabs/baseten-cli/cmd"
)

// WalkCommand converts a declarative cmdpkg.Command into the JSON-emittable
// Command at the given parent path. The returned node's Path is parentPath
// with c.Name appended.
func WalkCommand(parentPath []string, c cmdpkg.Command) Command {
path := append(append([]string{}, parentPath...), c.Name)
out := Command{
Name: c.Name,
Path: path,
Summary: c.Summary,
Description: c.Description,
IsLeaf: len(c.Children) == 0,
ArgsUsage: c.ArgsUsage,
ExactArgs: c.ExactArgs,
MaxArgs: c.MaxArgs,
DisableFlagParsing: c.DisableFlagParsing,
Flags: flagsFor(c),
}
applyOutput(&out, c.Output)
out.Errors = errorEntries(c.Errors)
for _, child := range c.Children {
out.Children = append(out.Children, WalkCommand(path, child))
}
return out
}

func applyOutput(dst *Command, spec cmdpkg.CommandOutputSpec) {
if spec == nil {
return
}
dst.TextDescription = spec.Text()
dst.JSONDescription = spec.JSON()
dst.JSONArrayStreamed = spec.JSONArrayStreamedBool()
if t := spec.JSONOutputType(); t != nil {
dst.JSONOutputType = t.String()
}
for _, ex := range spec.ExampleList() {
dst.Examples = append(dst.Examples, Example{Description: ex.Description, Command: ex.Command})
}
jq := spec.JQ()
if jq.Command != "" || jq.Description != "" {
dst.JQExample = &Example{Description: jq.Description, Command: jq.Command}
}
}

func flagsFor(c cmdpkg.Command) []Flag {
raw := c.LoadFlags()
if len(raw) == 0 {
return nil
}
out := make([]Flag, 0, len(raw))
for _, f := range raw {
out = append(out, Flag{
Name: f.Name,
Short: f.Short,
Description: f.Desc,
Default: f.Default,
Enum: f.Enum,
Required: f.Required,
Oneof: f.Oneof,
Type: f.Type.String(),
FieldName: f.FieldName,
Group: f.Group,
GroupPri: f.GroupPri,
})
}
return out
}

// Walk produces the full Schema for the given root command. cliVersion is
// embedded as-is (e.g. "v0.1.0", "dev"); generatedAt is the RFC3339 timestamp
// to embed.
func Walk(cliVersion, generatedAt string, root cmdpkg.Command) Schema {
return Schema{
SchemaVersion: SchemaVersion,
CLIVersion: cliVersion,
GeneratedAt: generatedAt,
Root: WalkCommand(nil, root),
StandardErrors: errorEntries(cmdpkg.StandardErrors()),
}
}

// errorEntries converts the framework's typed error descriptors into the
// JSON-emittable ErrorEntry shape.
func errorEntries(src []cmdpkg.ErrorDesc) []ErrorEntry {
if len(src) == 0 {
return nil
}
out := make([]ErrorEntry, 0, len(src))
for _, e := range src {
out = append(out, ErrorEntry{Name: e.Name, Code: int(e.Code), Meaning: e.Meaning})
}
return out
}
Loading
Loading