Skip to content

feat: quickstart --from flag #1291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 6, 2025
Merged
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
34 changes: 32 additions & 2 deletions cmd/quickstart.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type QuickstartFlags struct {
Schema string `json:"schema"`
OutDir string `json:"out-dir"`
TargetType string `json:"target"`
From string `json:"from"`
}

//go:embed sample_openapi.yaml
Expand Down Expand Up @@ -75,6 +76,11 @@ var quickstartCmd = &model.ExecutableCommand[QuickstartFlags]{
Shorthand: "t",
Description: fmt.Sprintf("language to generate sdk for (available options: [%s])", strings.Join(prompts.GetSupportedTargets(), ", ")),
},
flag.StringFlag{
Name: "from",
Shorthand: "f",
Description: "template to use for the quickstart command.\nCreate a new sandbox at https://app.speakeasy.com/sandbox",
},
},
}

Expand All @@ -97,8 +103,6 @@ func quickstartExec(ctx context.Context, flags QuickstartFlags) error {
return ErrWorkflowExists
}

log.From(ctx).PrintfStyled(styles.DimmedItalic, "\nYour first SDK is a few short questions away...\n")

quickstartObj := prompts.Quickstart{
WorkflowFile: &workflow.Workflow{
Version: workflow.WorkflowVersion,
Expand All @@ -116,6 +120,11 @@ func quickstartExec(ctx context.Context, flags QuickstartFlags) error {
quickstartObj.Defaults.TargetType = &flags.TargetType
}

if flags.From != "" {
quickstartObj.Defaults.Blueprint = &flags.From
quickstartObj.IsUsingBlueprint = true
Copy link
Contributor

Choose a reason for hiding this comment

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

Blueprint isn't super descriptive?

Copy link
Contributor

@mfbx9da4 mfbx9da4 Mar 6, 2025

Choose a reason for hiding this comment

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

This would be cleaner if we didn't have this special case, instead if we're using the from argument -> set the schema URL https://app.speakeasy.com/v1/schema/store/{ID}

}

nextState := prompts.SourceBase
for nextState != prompts.Complete {
stateFunc := prompts.StateMapping[nextState]
Expand Down Expand Up @@ -258,6 +267,27 @@ func quickstartExec(ctx context.Context, flags QuickstartFlags) error {
quickstartObj.WorkflowFile.Sources[sourceName].Inputs[0].Location = workflow.LocationString(referencePath)
}

// If we are using a blueprint template, the original location will be a
// tempfile. We want therefore to move the tempfile to the output directory,
// and update the workflow file to point to the new location.
if quickstartObj.IsUsingBlueprint {
oldInput := quickstartObj.WorkflowFile.Sources[sourceName].Inputs[0].Location

oldInputPath := oldInput.Resolve()
// parse the last part of the path to get the filename + extension
filename := filepath.Base(oldInputPath)

ext := filepath.Ext(filename)

newPath := filepath.Join(outDir, fmt.Sprintf("openapi%s", ext))

if err := os.Rename(oldInputPath, newPath); err != nil {
return errors.Wrapf(err, "failed to rename blueprint to openapi.yaml")
}

quickstartObj.WorkflowFile.Sources[sourceName].Inputs[0].Location = workflow.LocationString(newPath)
}

// Make sure the workflow file stays up to date
run.Migrate(ctx, quickstartObj.WorkflowFile)

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ require (
github.com/speakeasy-api/openapi-generation/v2 v2.541.0
github.com/speakeasy-api/openapi-overlay v0.10.1
github.com/speakeasy-api/sdk-gen-config v1.30.7
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.24.0
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.25.1
github.com/speakeasy-api/speakeasy-core v0.19.6
github.com/speakeasy-api/speakeasy-proxy v0.0.2
github.com/speakeasy-api/versioning-reports v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,8 @@ github.com/speakeasy-api/sdk-gen-config v1.30.7 h1:SE3ogoiczCn6IXFD3xOw9o+3EnfZZ
github.com/speakeasy-api/sdk-gen-config v1.30.7/go.mod h1:e9PjnCRHGa4K4EFKVU+kKmihOZjJ2V4utcU+274+bnQ=
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.24.0 h1:Lhzk66xMdSXe/H9EDgzJP5NVynyCKzcYs1vZTmzciD4=
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.24.0/go.mod h1:k9JD6Rj0+Iizc5COoLZHyRIOGGITpKZ2qBuFFO8SqNI=
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.25.1 h1:RoeWDoDS4H+z3whylb1HIb+ob4/ay4CuKnBku72Zv/c=
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.25.1/go.mod h1:k9JD6Rj0+Iizc5COoLZHyRIOGGITpKZ2qBuFFO8SqNI=
github.com/speakeasy-api/speakeasy-core v0.19.6 h1:w0VtRMzQCrFY6XEXdIJlywex8xROvoq5qlg6Acjvohg=
github.com/speakeasy-api/speakeasy-core v0.19.6/go.mod h1:M3ZBHTexZINVbpcz+nIEoaFw4Ti0Gc1rnZbdQjiFlaA=
github.com/speakeasy-api/speakeasy-go-sdk v1.8.1 h1:atzohw12oQ5ipaLb1q7ntTu4vvAgKDJsrvaUoOu6sw0=
Expand Down
67 changes: 64 additions & 3 deletions prompts/sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ import (

"github.com/charmbracelet/lipgloss"
"github.com/speakeasy-api/huh"
"github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/operations"
"github.com/speakeasy-api/speakeasy-core/openapi"

timeAgo "github.com/dustin/go-humanize"
humanize "github.com/dustin/go-humanize/english"
"github.com/speakeasy-api/speakeasy/internal/charm/styles"
"github.com/speakeasy-api/speakeasy/internal/remote"
"github.com/speakeasy-api/speakeasy/internal/sdk"

"github.com/iancoleman/strcase"
"github.com/pkg/errors"
"github.com/speakeasy-api/sdk-gen-config/workflow"
"github.com/speakeasy-api/speakeasy-core/auth"

charm_internal "github.com/speakeasy-api/speakeasy/internal/charm"
"github.com/speakeasy-api/speakeasy/registry"
)
Expand Down Expand Up @@ -180,8 +183,10 @@ func sourceBaseForm(ctx context.Context, quickstart *Quickstart) (*QuickstartSta
defer cancel()
recentGenerations, err := remote.GetRecentWorkspaceGenerations(timeout)

// Retrieve recent namespaces and check if there are any available.
hasRecentGenerations := err == nil && len(recentGenerations) > 0
hasBlueprint := quickstart.Defaults.Blueprint != nil && *quickstart.Defaults.Blueprint != ""

// Retrieve recent namespaces and check if there are any available. If --blueprint is provided, we will not check for recent generations.
hasRecentGenerations := !hasBlueprint && err == nil && len(recentGenerations) > 0

// Determine if we should use a remote source. Defaults to true before the user
// has interacted with the form.
Expand Down Expand Up @@ -213,7 +218,29 @@ func sourceBaseForm(ctx context.Context, quickstart *Quickstart) (*QuickstartSta
}
}

if quickstart.Defaults.SchemaPath != nil {
if hasBlueprint {
blueprintFile, err := fetchAndSaveBlueprint(ctx, *quickstart.Defaults.Blueprint)
if err == nil {
fileLocation = blueprintFile

fmt.Println(
styles.RenderInfoMessage(
fmt.Sprintf("Using sandbox session '%s'", *quickstart.Defaults.Blueprint),
) + "\n",
)
} else {
// fallthrough
fmt.Println(
styles.RenderInfoMessage(
fmt.Sprintf("Could not find sandbox session '%s'. Continuing with quickstart...", *quickstart.Defaults.Blueprint),
) + "\n",
)
}
}

if hasBlueprint && fileLocation != "" {
// noop
} else if quickstart.Defaults.SchemaPath != nil {
fileLocation = *quickstart.Defaults.SchemaPath
} else if useRemoteSource && selectedRegistryUri != "" {
// The workflow file will be updated with a registry based input like:
Expand Down Expand Up @@ -657,3 +684,37 @@ func configureRegistry(source *workflow.Source, orgSlug, workspaceSlug, sourceNa
source.Registry = registryEntry
return nil
}

var (
ErrMsgFailedToFetchBlueprint = errors.New("failed to fetch sandbox session")
ErrMsgFailedToSaveBlueprint = errors.New("failed to save sandbox session")
ErrMsgFailedToDecodeBlueprint = errors.New("failed to decode sandbox session")
)

func fetchAndSaveBlueprint(ctx context.Context, blueprintID string) (string, error) {
speakeasyClient, err := sdk.InitSDK()
if err != nil {
return "", err
}

schemaStoreItem, err := speakeasyClient.SchemaStore.GetSchemaStoreItem(ctx, &operations.GetSchemaStoreItemRequestBody{
ID: &blueprintID,
})
if err != nil {
return "", ErrMsgFailedToFetchBlueprint
}

tempDir := os.TempDir()
tempFile, err := os.Create(filepath.Join(tempDir, fmt.Sprintf("sandbox-%s.%s", schemaStoreItem.SchemaStoreItem.ID, schemaStoreItem.SchemaStoreItem.Format)))
if err != nil {
return "", ErrMsgFailedToSaveBlueprint
}
defer tempFile.Close()

_, err = tempFile.WriteString(schemaStoreItem.SchemaStoreItem.Spec)
if err != nil {
return "", ErrMsgFailedToSaveBlueprint
}

return tempFile.Name(), nil
}
2 changes: 2 additions & 0 deletions prompts/statemappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ type Quickstart struct {
LanguageConfigs map[string]*config.Configuration
Defaults Defaults
IsUsingSampleOpenAPISpec bool
IsUsingBlueprint bool
SDKName string
}

type Defaults struct {
SchemaPath *string
TargetType *string
Blueprint *string
}

// Define constants using iota
Expand Down
Loading