Skip to content

Commit 6ff00bf

Browse files
authored
perf: consume pre-rendered usage snippets to skip duplicate AST resolution (#1870)
- Adds `GenerateOverlayFromRawSnippets` to build code sample overlays from pre-rendered snippet output, skipping a redundant `usagegen.Generate()` call - Threads `RenderedUsageSnippets` from `sdkgen.Generate()` through `GenerationAccess` to `runCodeSamples` - Falls back to existing `GenerateOverlay` path when pre-rendered snippets are unavailable
1 parent 9d1355a commit 6ff00bf

File tree

5 files changed

+65
-23
lines changed

5 files changed

+65
-23
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ require (
3838
github.com/speakeasy-api/huh v1.1.2
3939
github.com/speakeasy-api/jq v0.1.1-0.20251107233444-84d7e49e84a4
4040
github.com/speakeasy-api/openapi v1.16.1
41-
github.com/speakeasy-api/openapi-generation/v2 v2.814.0
41+
github.com/speakeasy-api/openapi-generation/v2 v2.816.0
4242
github.com/speakeasy-api/sdk-gen-config v1.49.0
4343
github.com/speakeasy-api/speakeasy-agent-mode-content v0.2.0
4444
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.26.7

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,8 @@ github.com/speakeasy-api/libopenapi v0.21.9-fixhiddencomps-fixed h1:PL/kpBY5vkBm
538538
github.com/speakeasy-api/libopenapi v0.21.9-fixhiddencomps-fixed/go.mod h1:Gc8oQkjr2InxwumK0zOBtKN9gIlv9L2VmSVIUk2YxcU=
539539
github.com/speakeasy-api/openapi v1.16.1 h1:eUFWR7wqqT2GNXqOPMTbbW1zJE28qT1m1dWmNMEhviI=
540540
github.com/speakeasy-api/openapi v1.16.1/go.mod h1:aiVj+JnirrwZDtKegt0hQrj/ixl3v17EkN2YGnTuSro=
541-
github.com/speakeasy-api/openapi-generation/v2 v2.814.0 h1:XhtCx72DW1JJrIoNfYCbN6vVoGlp8Lh2FwbL92QCDyM=
542-
github.com/speakeasy-api/openapi-generation/v2 v2.814.0/go.mod h1:cY7IhImqvA0lY8iCzATzqZTFERVpfsmnzPsuxqCrzHQ=
541+
github.com/speakeasy-api/openapi-generation/v2 v2.816.0 h1:0W23h9FGGh3X4DoqKDJvZqk9GVH/GghkUL+6EmeQ4tg=
542+
github.com/speakeasy-api/openapi-generation/v2 v2.816.0/go.mod h1:cY7IhImqvA0lY8iCzATzqZTFERVpfsmnzPsuxqCrzHQ=
543543
github.com/speakeasy-api/openapi/openapi/linter/customrules v0.0.0-20260206023826-2483fb8e98b4 h1:gV+lYeVNNJG9X3Sl9Su3cRh1iF/oNqzvb5Ijq2QR8jY=
544544
github.com/speakeasy-api/openapi/openapi/linter/customrules v0.0.0-20260206023826-2483fb8e98b4/go.mod h1:1zQpVio7X6QJDtyNdUguCgZ+IC7CzKhhjvNgJdvGVF0=
545545
github.com/speakeasy-api/sdk-gen-config v1.49.0 h1:zwOBNfQtip564bOcWEUSwwJxSGQGkWA15R8xIxFTgHs=

internal/run/target.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/hashicorp/go-version"
11+
"github.com/speakeasy-api/openapi-generation/v2/pkg/generate"
1112
sdkGenConfig "github.com/speakeasy-api/sdk-gen-config"
1213
"github.com/speakeasy-api/sdk-gen-config/workflow"
1314
"github.com/speakeasy-api/speakeasy-core/auth"
@@ -240,6 +241,7 @@ func (w *Workflow) runTarget(ctx context.Context, target string) (*SourceResult,
240241
StreamableGeneration: w.StreamableGeneration,
241242
ReleaseNotes: changelogContent,
242243
WorkflowStep: genStep,
244+
RenderUsageSnippets: t.CodeSamplesEnabled(),
243245
},
244246
)
245247
if err != nil {
@@ -253,7 +255,7 @@ func (w *Workflow) runTarget(ctx context.Context, target string) (*SourceResult,
253255

254256
if t.CodeSamplesEnabled() {
255257
codeSamplesStep := rootStep.NewSubstep("Generating Code Samples")
256-
namespaceName, digest, err := w.runCodeSamples(ctx, codeSamplesStep, *t.CodeSamples, t.Target, sourcePath, t.Output)
258+
namespaceName, digest, err := w.runCodeSamples(ctx, codeSamplesStep, *t.CodeSamples, t.Target, sourcePath, t.Output, generationAccess.RenderedUsageSnippets)
257259
if err != nil {
258260
// Block by default. Only warn if explicitly set to non-blocking
259261
if t.CodeSamples.Blocking == nil || *t.CodeSamples.Blocking {
@@ -298,8 +300,10 @@ func (w *Workflow) runTarget(ctx context.Context, target string) (*SourceResult,
298300
return sourceRes, &targetResult, nil
299301
}
300302

301-
// Returns codeSamples namespace name and digest
302-
func (w *Workflow) runCodeSamples(ctx context.Context, codeSamplesStep *workflowTracking.WorkflowStep, codeSamples workflow.CodeSamples, target, sourcePath string, targetOutputPath *string) (string, string, error) {
303+
// Returns codeSamples namespace name and digest.
304+
// If preRendered is non-nil with content, it is used to build the overlay directly,
305+
// avoiding a second AST resolution pass.
306+
func (w *Workflow) runCodeSamples(ctx context.Context, codeSamplesStep *workflowTracking.WorkflowStep, codeSamples workflow.CodeSamples, target, sourcePath string, targetOutputPath *string, preRendered *generate.RenderedUsageSnippets) (string, string, error) {
303307
configPath := "."
304308
writeFileLocation := codeSamples.Output
305309

@@ -312,7 +316,14 @@ func (w *Workflow) runCodeSamples(ctx context.Context, codeSamplesStep *workflow
312316
}
313317
}
314318

315-
overlayString, err := codesamples.GenerateOverlay(ctx, sourcePath, "", "", configPath, writeFileLocation, []string{target}, true, false, codeSamples)
319+
var overlayString string
320+
var err error
321+
322+
if preRendered != nil && preRendered.RawOutput != "" {
323+
overlayString, err = codesamples.GenerateOverlayFromRawSnippets(ctx, preRendered.RawOutput, target, sourcePath, writeFileLocation, true, codeSamples)
324+
} else {
325+
overlayString, err = codesamples.GenerateOverlay(ctx, sourcePath, "", "", configPath, writeFileLocation, []string{target}, true, false, codeSamples)
326+
}
316327
if err != nil {
317328
return "", "", err
318329
}

internal/sdkgen/sdkgen.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ import (
3838
var PromptForCustomCode = prompts.PromptForCustomCode
3939

4040
type GenerationAccess struct {
41-
AccessAllowed bool
42-
Message string
43-
Level *shared.Level
41+
AccessAllowed bool
42+
Message string
43+
Level *shared.Level
44+
RenderedUsageSnippets *generate.RenderedUsageSnippets // pre-rendered snippets from SDK generation (nil if not requested)
4445
}
4546

4647
type CancellableGeneration struct {
@@ -85,6 +86,10 @@ type GenerateOptions struct {
8586
// WorkflowStep enables running prompts through the workflow visualizer.
8687
// If nil, prompts will run directly without visualizer integration.
8788
WorkflowStep *workflowTracking.WorkflowStep
89+
90+
// RenderUsageSnippets opts in to pre-rendering standalone usage snippets
91+
// during SDK generation, reusing the already-resolved AST.
92+
RenderUsageSnippets bool
8893
}
8994

9095
func Generate(ctx context.Context, opts GenerateOptions) (*GenerationAccess, error) {
@@ -178,6 +183,9 @@ func Generate(ctx context.Context, opts GenerateOptions) (*GenerationAccess, err
178183
if opts.SkipVersioning {
179184
generatorOpts = append(generatorOpts, generate.WithSkipVersioning(opts.SkipVersioning))
180185
}
186+
if opts.RenderUsageSnippets {
187+
generatorOpts = append(generatorOpts, generate.WithRenderUsageSnippets())
188+
}
181189

182190
// Track the current generation step for error reporting
183191
failedStepMessage := ""
@@ -320,8 +328,9 @@ func Generate(ctx context.Context, opts GenerateOptions) (*GenerationAccess, err
320328
}
321329

322330
return &GenerationAccess{
323-
AccessAllowed: generationAccess,
324-
Message: message,
331+
AccessAllowed: generationAccess,
332+
Message: message,
333+
RenderedUsageSnippets: g.GetRenderedUsageSnippets(),
325334
}, nil
326335
}
327336

pkg/codesamples/codesamples.go

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,15 @@ type CodeSampleExampleSource struct {
3333
}
3434

3535
func GenerateOverlay(ctx context.Context, schema, header, token, configPath, overlayFilename string, langs []string, isWorkflow bool, isSilent bool, opts workflow.CodeSamples) (string, error) {
36-
targetToCodeSamples := map[string][]usagegen.UsageSnippet{}
37-
isJSON := filepath.Ext(schema) == ".json"
38-
3936
if isSilent {
4037
logger := log.From(ctx)
4138
var logs bytes.Buffer
4239
logCapture := logger.WithWriter(&logs)
4340
ctx = log.With(ctx, logCapture)
4441
}
4542

43+
var allSnippets []usagegen.UsageSnippet
44+
4645
for _, lang := range langs {
4746
usageOutput := &bytes.Buffer{}
4847

@@ -72,11 +71,34 @@ func GenerateOverlay(ctx context.Context, schema, header, token, configPath, ove
7271
return "", err
7372
}
7473

75-
for _, snippet := range snippets {
76-
target := overlay.NewTargetSelector(snippet.Path, snippet.Method)
74+
allSnippets = append(allSnippets, snippets...)
75+
}
76+
77+
return buildOverlay(allSnippets, schema, langs[0], overlayFilename, isWorkflow, opts)
78+
}
7779

78-
targetToCodeSamples[target] = append(targetToCodeSamples[target], snippet)
79-
}
80+
// GenerateOverlayFromRawSnippets builds a code samples overlay from pre-rendered
81+
// raw snippet output, bypassing the need to create a new Generator and re-resolve
82+
// the AST. The rawOutput is expected to contain "// Usage snippet provided for ..."
83+
// sections as produced by standalone.ts.
84+
func GenerateOverlayFromRawSnippets(ctx context.Context, rawOutput, lang, schema string, overlayFilename string, isWorkflow bool, opts workflow.CodeSamples) (string, error) {
85+
snippets, err := usagegen.ParseUsageOutput(lang, rawOutput)
86+
if err != nil {
87+
return "", err
88+
}
89+
90+
log.From(ctx).Infof("\nUsing pre-rendered usage snippets for %s\n\n", lang)
91+
92+
return buildOverlay(snippets, schema, lang, overlayFilename, isWorkflow, opts)
93+
}
94+
95+
func buildOverlay(snippets []usagegen.UsageSnippet, schema, lang, overlayFilename string, isWorkflow bool, opts workflow.CodeSamples) (string, error) {
96+
isJSON := filepath.Ext(schema) == ".json"
97+
98+
targetToCodeSamples := map[string][]usagegen.UsageSnippet{}
99+
for _, snippet := range snippets {
100+
target := overlay.NewTargetSelector(snippet.Path, snippet.Method)
101+
targetToCodeSamples[target] = append(targetToCodeSamples[target], snippet)
80102
}
81103

82104
var actions []overlay.Action
@@ -102,10 +124,10 @@ func GenerateOverlay(ctx context.Context, schema, header, token, configPath, ove
102124
}
103125

104126
if isWorkflow {
105-
title = fmt.Sprintf("CodeSamples overlay for %s target", langs[0])
127+
title = fmt.Sprintf("CodeSamples overlay for %s target", lang)
106128
}
107129

108-
overlay := &overlay.Overlay{
130+
o := &overlay.Overlay{
109131
Version: "1.0.0",
110132
Info: overlay.Info{
111133
Title: title,
@@ -115,10 +137,10 @@ func GenerateOverlay(ctx context.Context, schema, header, token, configPath, ove
115137
}
116138

117139
if !isWorkflow {
118-
overlay.Extends = extends
140+
o.Extends = extends
119141
}
120142

121-
overlayString, err := overlay.ToString()
143+
overlayString, err := o.ToString()
122144
if err != nil {
123145
return "", err
124146
}

0 commit comments

Comments
 (0)