├── .github ├── dependabot.yml └── workflows │ ├── documentation.yml │ ├── release.yml │ └── validate.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .vscode ├── launch.template.json └── tasks.json ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── ask.go ├── auth.go ├── billing.go ├── bump.go ├── clean.go ├── configure.go ├── defaultCodeSamples.go ├── docs │ └── main.go ├── generate │ ├── codesamples.go │ ├── generate.go │ ├── sdk.go │ ├── supportedTargets.go │ └── usage.go ├── languageserver.go ├── lint │ └── lint.go ├── merge.go ├── migrate.go ├── openapi │ ├── openapi.go │ └── transform.go ├── overlay.go ├── proxy.go ├── quickstart.go ├── root.go ├── run.go ├── sample_openapi.yaml ├── status.go ├── suggest.go ├── tag.go ├── testcmd.go └── update.go ├── go.mod ├── go.sum ├── install.sh ├── integration ├── main_test.go ├── overlay_test.go ├── resources │ ├── codeSamples-JSON.yaml │ ├── codeSamples.yaml │ ├── converted.yaml │ ├── formatted.yaml │ ├── normalize-input.yaml │ ├── normalize-output.yaml │ ├── overlay │ │ ├── openapi-overlayed-expected.yaml │ │ ├── openapi.yaml │ │ └── overlay.yaml │ ├── part1.yaml │ ├── part2.yaml │ ├── renameOperationOverlay.yaml │ ├── spec.yaml │ ├── swagger.json │ ├── swagger.yaml │ └── unformatted.yaml ├── utils.go ├── workflow_registry_test.go └── workflow_test.go ├── internal ├── arazzo │ └── arazzo.go ├── ask │ └── ask.go ├── auth │ └── auth.go ├── cache │ └── cache.go ├── changes │ └── openapiChanges.go ├── charm │ ├── formModel.go │ ├── formTheme.go │ ├── internalModel.go │ ├── styles │ │ ├── markdown.go │ │ └── styles.go │ ├── transformAccessor.go │ └── utils.go ├── config │ └── config.go ├── defaultcodesamples │ ├── .gitignore │ ├── defaultcodesamples.go │ ├── out │ │ └── defaultcodesamples.js │ ├── package-lock.json │ ├── package.json │ ├── patches │ │ └── @jsdevtools+ono+7.1.3.patch │ ├── src │ │ ├── generateCodeSamplesOverlay.ts │ │ ├── main.ts │ │ └── oas-normalize │ │ │ ├── .sink.d.ts │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ │ └── readme.md │ ├── test │ │ └── test.test.ts │ └── tsconfig.json ├── docs │ └── docs.go ├── download │ └── download.go ├── env │ └── env.go ├── fs │ └── fs.go ├── git │ └── repository.go ├── github │ ├── github.go │ └── github_test.go ├── interactivity │ ├── button.go │ ├── hiddenCommands.go │ ├── interactiveexec.go │ ├── listSelect.go │ ├── multiInput.go │ ├── simpleConfirm.go │ ├── simpleInput.go │ ├── spinner.go │ └── tabs.go ├── links │ └── links.go ├── locks │ ├── locks.go │ └── locks_test.go ├── log │ ├── log.go │ ├── logListener.go │ ├── logProxy.go │ └── utils.go ├── markdown │ ├── markdown.go │ └── markdown_test.go ├── migrate │ └── migrate.go ├── model │ ├── command.go │ └── flag │ │ ├── booleanFlag.go │ │ ├── enumFlag.go │ │ ├── flag.go │ │ ├── intFlag.go │ │ ├── mapFlag.go │ │ ├── stringFlag.go │ │ └── stringSliceFlag.go ├── remote │ └── sources.go ├── reports │ └── reports.go ├── run │ ├── criticalWarnings.go │ ├── frozenSource.go │ ├── merge.go │ ├── minimumViableSpec.go │ ├── overlay.go │ ├── remote.go │ ├── remote_test.go │ ├── run.go │ ├── source.go │ ├── sourceTracking.go │ ├── target.go │ ├── testing.go │ ├── transform.go │ └── workflow.go ├── schemas │ ├── document.go │ └── format.go ├── sdk │ └── sdk.go ├── sdkgen │ └── sdkgen.go ├── singleton │ └── singleton.go ├── studio │ ├── launchStudio.go │ ├── modifications │ │ └── overlay.go │ ├── oas_studio.yaml │ ├── sdk │ │ ├── .genignore │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .speakeasy │ │ │ ├── gen.lock │ │ │ ├── gen.yaml │ │ │ ├── speakeasy-modifications-overlay.yaml │ │ │ ├── workflow.lock │ │ │ └── workflow.yaml │ │ ├── CONTRIBUTING.md │ │ ├── README.md │ │ ├── USAGE.md │ │ ├── docs │ │ │ ├── models │ │ │ │ ├── components │ │ │ │ │ ├── auth.md │ │ │ │ │ ├── codesamples.md │ │ │ │ │ ├── data.md │ │ │ │ │ ├── diagnostic.md │ │ │ │ │ ├── document.md │ │ │ │ │ ├── fallbackcodesamples.md │ │ │ │ │ ├── healthresponse.md │ │ │ │ │ ├── httpmetadata.md │ │ │ │ │ ├── overlay.md │ │ │ │ │ ├── overlaycomparerequestbody.md │ │ │ │ │ ├── overlaycompareresponse.md │ │ │ │ │ ├── overlaydocument.md │ │ │ │ │ ├── publishing.md │ │ │ │ │ ├── runrequestbody.md │ │ │ │ │ ├── runresponse.md │ │ │ │ │ ├── runresponsedata.md │ │ │ │ │ ├── security.md │ │ │ │ │ ├── source.md │ │ │ │ │ ├── sourceregistry.md │ │ │ │ │ ├── sourceresponsedata.md │ │ │ │ │ ├── suggestresponse.md │ │ │ │ │ ├── target.md │ │ │ │ │ ├── targetrunsummary.md │ │ │ │ │ ├── targetspecificinputs.md │ │ │ │ │ └── workflow.md │ │ │ │ └── operations │ │ │ │ │ ├── checkhealthresponse.md │ │ │ │ │ ├── exitresponse.md │ │ │ │ │ ├── generateoverlayresponse.md │ │ │ │ │ ├── getrunresponse.md │ │ │ │ │ ├── option.md │ │ │ │ │ ├── runresponse.md │ │ │ │ │ └── suggestmethodnamesresponse.md │ │ │ └── sdks │ │ │ │ ├── health │ │ │ │ └── README.md │ │ │ │ ├── run │ │ │ │ └── README.md │ │ │ │ ├── sdk │ │ │ │ └── README.md │ │ │ │ └── suggest │ │ │ │ └── README.md │ │ ├── health.go │ │ ├── internal │ │ │ ├── hooks │ │ │ │ ├── hooks.go │ │ │ │ └── registration.go │ │ │ └── utils │ │ │ │ ├── contenttype.go │ │ │ │ ├── env.go │ │ │ │ ├── form.go │ │ │ │ ├── headers.go │ │ │ │ ├── json.go │ │ │ │ ├── pathparams.go │ │ │ │ ├── queryparams.go │ │ │ │ ├── requestbody.go │ │ │ │ ├── retries.go │ │ │ │ ├── security.go │ │ │ │ └── utils.go │ │ ├── models │ │ │ ├── components │ │ │ │ ├── auth.go │ │ │ │ ├── codesamples.go │ │ │ │ ├── diagnostic.go │ │ │ │ ├── document.go │ │ │ │ ├── fallbackcodesamples.go │ │ │ │ ├── healthresponse.go │ │ │ │ ├── httpmetadata.go │ │ │ │ ├── overlay.go │ │ │ │ ├── overlaycomparerequestbody.go │ │ │ │ ├── overlaycompareresponse.go │ │ │ │ ├── publishing.go │ │ │ │ ├── runrequestbody.go │ │ │ │ ├── runresponse.go │ │ │ │ ├── runresponsedata.go │ │ │ │ ├── security.go │ │ │ │ ├── source.go │ │ │ │ ├── sourceregistry.go │ │ │ │ ├── sourceresponsedata.go │ │ │ │ ├── suggestresponse.go │ │ │ │ ├── target.go │ │ │ │ ├── targetrunsummary.go │ │ │ │ ├── targetspecificinputs.go │ │ │ │ └── workflow.go │ │ │ ├── operations │ │ │ │ ├── checkhealth.go │ │ │ │ ├── exit.go │ │ │ │ ├── generateoverlay.go │ │ │ │ ├── getrun.go │ │ │ │ ├── options.go │ │ │ │ ├── run.go │ │ │ │ ├── suggestmethodnames.go │ │ │ │ └── updatesource.go │ │ │ └── sdkerrors │ │ │ │ └── sdkerror.go │ │ ├── retry │ │ │ └── config.go │ │ ├── run.go │ │ ├── sdk.go │ │ ├── suggest.go │ │ └── types │ │ │ ├── bigint.go │ │ │ ├── date.go │ │ │ ├── datetime.go │ │ │ ├── decimal.go │ │ │ ├── pointers.go │ │ │ └── stream │ │ │ └── stream.go │ ├── studioHandlers.go │ └── studioHelpers.go ├── suggest │ ├── diagnose.go │ ├── errorCodes │ │ ├── diagnose.go │ │ ├── errorCodes.go │ │ ├── errorCodes_test.go │ │ ├── errorGroups.go │ │ └── testData │ │ │ ├── nameConflict.yaml │ │ │ ├── nameConflict_expected.yaml │ │ │ ├── petstore.yaml │ │ │ ├── petstore_expected.yaml │ │ │ ├── reuse4xx.yaml │ │ │ ├── reuse4xx_expected.yaml │ │ │ ├── simple.yaml │ │ │ └── simple_expected.yaml │ └── suggest.go ├── testcmd │ ├── runner.go │ ├── runner_opt.go │ └── testing.go ├── updates │ └── updates.go ├── usagegen │ └── usagegen.go ├── utils │ ├── openapi.go │ └── utils.go ├── validation │ ├── config.go │ ├── customConfigValidators.go │ └── openapi.go └── workflowTracking │ ├── workflowCliVisualizer.go │ ├── workflowMermaidVisualizer.go │ └── workflowTracking.go ├── main.go ├── pkg ├── codesamples │ └── codesamples.go ├── merge │ ├── merge.go │ ├── merge_test.go │ └── testdata │ │ └── .gitignore ├── overlay │ ├── overlay.go │ ├── overlay_test.go │ └── testdata │ │ ├── base.json │ │ ├── base.yaml │ │ ├── components.yaml │ │ ├── expected.json │ │ ├── expected.yaml │ │ ├── expectedWrapped.yaml │ │ ├── overlay-v2.yaml │ │ ├── overlay.yaml │ │ └── strict-failure.yaml └── transform │ ├── cleanup.go │ ├── convertSwagger.go │ ├── convertSwagger_test.go │ ├── filterOperations.go │ ├── filterOperations_test.go │ ├── format.go │ ├── format_test.go │ ├── normalize.go │ ├── normalize_test.go │ ├── removeUnused.go │ └── transformer.go ├── prompts ├── configs.go ├── github.go ├── sources.go ├── statemappings.go ├── targets.go ├── terraform_release.yaml ├── terraform_releaser.yaml └── utils.go ├── registry ├── tagging.go └── utils.go └── scripts ├── completions.go ├── semver.bash └── upgrade.bash /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | registries: 3 | github-private: 4 | type: git 5 | url: https://github.com 6 | username: x-access-token 7 | password: ${{secrets.BOT_REPO_TOKEN}} 8 | updates: 9 | - package-ecosystem: github-actions 10 | directory: "/" 11 | # Try to group minor and patch updates for less toil 12 | groups: 13 | github-actions-minor-and-patch: 14 | applies-to: version-updates 15 | dependency-type: production 16 | update-types: 17 | - minor 18 | - patch 19 | schedule: 20 | interval: weekly 21 | - package-ecosystem: gomod 22 | directory: "/" 23 | # Try to group minor and patch updates for less toil 24 | groups: 25 | gomod-minor-and-patch: 26 | applies-to: version-updates 27 | dependency-type: production 28 | exclude-patterns: 29 | # Does not necessarily follow semver, raise individual PR for ease 30 | - "github.com/speakeasy-api/speakeasy-core" 31 | update-types: 32 | - minor 33 | - patch 34 | ignore: 35 | # Updated via ./scripts/upgrade.bash 36 | - dependency-name: "github.com/speakeasy-api/openapi-generation/v2" 37 | registries: 38 | - github-private 39 | schedule: 40 | interval: weekly 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | permissions: 8 | contents: write 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: 13 | group: windows-latest-large 14 | if: startsWith(github.event.head_commit.message, 'ci:') != true 15 | steps: 16 | - name: Setup environment 17 | run: |- 18 | chcp 65001 #set code page to utf-8 19 | echo ("GOPRIVATE=github.com/speakeasy-api") >> $env:GITHUB_ENV 20 | - name: Checkout repository 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | fetch-depth: 0 24 | - name: Conventional Commits 25 | uses: TriPSs/conventional-changelog-action@67139193614f5b9e8db87da1bd4240922b34d765 # v6.0.0 26 | with: 27 | github-token: ${{ secrets.github_token }} 28 | skip-commit: "true" 29 | output-file: "false" 30 | skip-on-empty: "false" 31 | preset: conventionalcommits 32 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 33 | with: 34 | go-version-file: "go.mod" 35 | # Reference: https://github.com/actions/setup-go/issues/495 36 | cache: false 37 | # More assembly might be required: Docker logins, GPG, etc. It all depends 38 | # on your needs. 39 | - name: Configure git for private modules 40 | run: git config --global url."https://speakeasybot:${{ secrets.BOT_REPO_TOKEN }}@github.com".insteadOf "https://github.com" 41 | - name: Setup Choco 42 | uses: crazy-max/ghaction-chocolatey@6828f16489ec8d2968b55066766cb41f0d278f2a # v3.3.0 43 | with: 44 | args: --version 45 | - name: goreleaser 46 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 47 | with: 48 | distribution: goreleaser 49 | version: latest 50 | args: release --clean 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.BOT_REPO_TOKEN }} 53 | CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} 54 | - name: Trigger changelog reconcile 55 | run: gh workflow run changelog-reconcile.yml --repo speakeasy-api/marketing-site --ref main 56 | env: 57 | GH_TOKEN: ${{ secrets.BOT_REPO_TOKEN }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | /docs/ 3 | bin/ 4 | .idea/ 5 | *.iml 6 | summary.md 7 | /.speakeasy/ 8 | temp/ 9 | integration/temp 10 | .DS_Store 11 | .vscode/launch.json 12 | __debug_bin* 13 | /completions/ 14 | 15 | **/.claude/settings.local.json 16 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | issues: 3 | max-issues-per-linter: 0 4 | max-same-issues: 0 5 | formatters: 6 | enable: 7 | - gofmt 8 | linters: 9 | default: none 10 | enable: 11 | # - copyloopvar 12 | # - durationcheck 13 | # - errcheck 14 | # - forcetypeassert 15 | # - gocheckcompilerdirectives 16 | - govet 17 | # - ineffassign 18 | # - intrange 19 | # - loggercheck 20 | # - makezero 21 | # - mirror 22 | - misspell 23 | # - nilerr 24 | # - nolintlint 25 | # - perfsprint 26 | # - prealloc 27 | # - predeclared 28 | # - spancheck 29 | # - staticcheck 30 | # - testifylint 31 | # - unconvert 32 | # - unparam 33 | # - unused 34 | # - usestdlibvars 35 | # - usetesting 36 | # - wastedassign 37 | -------------------------------------------------------------------------------- /.vscode/launch.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "speakeasy run", 5 | "type": "go", 6 | "request": "launch", 7 | "program": "main.go", 8 | "cwd": "/tmp/studio-test-6840674974721771490", 9 | "args": ["run", "--watch"] 10 | }, 11 | { 12 | "name": "Debug CLI headless", 13 | "type": "go", 14 | "debugAdapter": "dlv-dap", 15 | "request": "attach", 16 | "mode": "remote", 17 | "remotePath": "${workspaceFolder}", 18 | "port": 2345, 19 | "host": "127.0.0.1", 20 | "preLaunchTask": "Run headless dlv" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run headless dlv", 6 | "type": "process", 7 | "command": [ 8 | "dlv", 9 | ], 10 | "args": [ 11 | "debug", 12 | "--headless", 13 | "--listen=:2345", 14 | "--api-version=2", 15 | "${workspaceFolder}/main.go", 16 | "--", 17 | "${input:command}" 18 | ], 19 | "isBackground": true, 20 | "problemMatcher": { 21 | "owner": "go", 22 | "fileLocation": "relative", 23 | "pattern": { 24 | "regexp": "^couldn't start listener:", // error if matched 25 | }, 26 | "background": { 27 | "activeOnStart": true, 28 | "beginsPattern": "^API server listening at:", 29 | "endsPattern": "^Got a connection, launched process" // success if matched 30 | } 31 | } 32 | } 33 | ], 34 | "inputs": [ 35 | { 36 | "id": "command", 37 | "type": "promptString", 38 | "default": "quickstart", 39 | "description": "CLI command to debug" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: * 2 | SHELL = /usr/bin/env bash 3 | 4 | upgrade: 5 | ./scripts/upgrade.bash 6 | 7 | docs: 8 | go run cmd/docs/main.go 9 | 10 | docsite: 11 | go run cmd/docs/main.go -out-dir ../speakeasy-registry/web/packages/docsite/docs/speakeasy-cli -doc-site -------------------------------------------------------------------------------- /cmd/ask.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "github.com/speakeasy-api/speakeasy/internal/ask" 6 | "github.com/speakeasy-api/speakeasy/internal/model" 7 | "github.com/speakeasy-api/speakeasy/internal/model/flag" 8 | ) 9 | 10 | type AskFlags struct { 11 | Message string `json:"message"` 12 | SessionID string `json:"sessionID,omitempty"` 13 | } 14 | 15 | var AskCmd = &model.ExecutableCommand[AskFlags]{ 16 | Usage: "ask", 17 | Short: "Starts a conversation with Speakeasy trained AI", 18 | Long: "Starts a conversation with Speakeasy trained AI. Ask about OpenAPI, Speakeasy, configuring SDKs, or anything else you need help with.", 19 | Run: AskFunc, 20 | RequiresAuth: false, 21 | Flags: []flag.Flag{ 22 | flag.StringFlag{ 23 | Name: "message", 24 | Shorthand: "m", 25 | Description: "Your question for AI.", 26 | Required: false, 27 | }, 28 | }, 29 | } 30 | 31 | func AskFunc(ctx context.Context, flags AskFlags) error { 32 | return ask.RunInteractiveChatSession(ctx, flags.Message, flags.SessionID) 33 | } 34 | -------------------------------------------------------------------------------- /cmd/defaultCodeSamples.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/speakeasy-api/speakeasy/internal/defaultcodesamples" 8 | "github.com/speakeasy-api/speakeasy/internal/log" 9 | "github.com/speakeasy-api/speakeasy/internal/model" 10 | "github.com/speakeasy-api/speakeasy/internal/model/flag" 11 | ) 12 | 13 | var defaultCodeSamplesCmd = &model.ExecutableCommand[defaultcodesamples.DefaultCodeSamplesFlags]{ 14 | Hidden: true, 15 | Usage: "default-code-samples", 16 | Aliases: []string{""}, 17 | Short: "Utility for generating default code samples", 18 | Long: `The "default-code-samples" command allows you to generate code samples which use native language primitives for a given OpenAPI document.`, 19 | Run: defaultCodeSamples, 20 | Flags: []flag.Flag{ 21 | flag.StringFlag{ 22 | Name: "schema", 23 | Shorthand: "s", 24 | Description: "Path to the OpenAPI schema", 25 | Required: true, 26 | }, 27 | flag.StringFlag{ 28 | Name: "language", 29 | Shorthand: "l", 30 | Description: "Language to generate code samples for", 31 | Required: true, 32 | }, 33 | flag.StringFlag{ 34 | Name: "out", 35 | Shorthand: "o", 36 | Description: "Output directory for generated code samples", 37 | Required: true, 38 | }, 39 | }, 40 | } 41 | 42 | // You can test this command locally with the following command: 43 | // cd internal/defaultcodesamples/ && pnpm i && pnpm run build && cd - && go run main.go default-code-samples -s ./integration/resources/spec.yaml -l node -o /tmp/overlay.yaml 44 | func defaultCodeSamples(ctx context.Context, flags defaultcodesamples.DefaultCodeSamplesFlags) error { 45 | err := defaultcodesamples.DefaultCodeSamples(ctx, flags) 46 | if err != nil { 47 | err = fmt.Errorf("failed to generate default code samples: %w", err) 48 | log.From(ctx).Error(err.Error()) 49 | return err 50 | } 51 | 52 | log.From(ctx).Successf("Successfully generated default code samples for %s output to %s", flags.SchemaPath, flags.Out) 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /cmd/docs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "regexp" 9 | "slices" 10 | 11 | "github.com/speakeasy-api/speakeasy/cmd" 12 | "github.com/speakeasy-api/speakeasy/internal/docs" 13 | ) 14 | 15 | var linkRegex = regexp.MustCompile(`\((.*?\.md)\)`) 16 | 17 | func main() { 18 | outDir := flag.String("out-dir", "./docs", "The directory to output the docs to") 19 | flag.Parse() 20 | 21 | cmd.Init("", "") 22 | 23 | root := cmd.GetRootCommand() 24 | 25 | root.DisableAutoGenTag = true 26 | 27 | exclusionList := []string{ 28 | filepath.Join(*outDir, "getting-started.mdx"), 29 | filepath.Join(*outDir, "_meta.tsx"), 30 | } 31 | 32 | if _, err := removeDocs(*outDir, exclusionList); err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | if err := docs.GenerateDocs(root, *outDir); err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func removeDocs(outDir string, exclusionList []string) (bool, error) { 42 | items, err := os.ReadDir(outDir) 43 | if err != nil { 44 | return false, err 45 | } 46 | 47 | empty := true 48 | 49 | for _, item := range items { 50 | if item.IsDir() { 51 | empty, err := removeDocs(filepath.Join(outDir, item.Name()), exclusionList) 52 | if err != nil { 53 | return false, err 54 | } 55 | 56 | if empty { 57 | if err := os.Remove(filepath.Join(outDir, item.Name())); err != nil { 58 | return false, err 59 | } 60 | } 61 | } else { 62 | if slices.Contains(exclusionList, filepath.Join(outDir, item.Name())) { 63 | empty = false 64 | continue 65 | } 66 | 67 | if err := os.Remove(filepath.Join(outDir, item.Name())); err != nil { 68 | return false, err 69 | } 70 | } 71 | } 72 | 73 | return empty, nil 74 | } 75 | -------------------------------------------------------------------------------- /cmd/generate/supportedTargets.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/speakeasy-api/speakeasy/internal/model" 9 | "github.com/speakeasy-api/speakeasy/internal/model/flag" 10 | ) 11 | 12 | type supportedTargetsFlags struct{} 13 | 14 | var suportedTargetsCmd = &model.ExecutableCommand[supportedTargetsFlags]{ 15 | Usage: "supported-targets", 16 | Short: "Returns a list of supported generation targets.", 17 | Run: runSupportedTargets, 18 | Flags: []flag.Flag{}, 19 | Hidden: true, 20 | } 21 | 22 | func runSupportedTargets(ctx context.Context, flags supportedTargetsFlags) error { 23 | // Do not change this output structure, the sdk-generation-action depends on it 24 | fmt.Println(strings.Join(GeneratorSupportedTargetNames(), ",")) 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /cmd/languageserver.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/speakeasy-api/openapi-generation/v2/languageserver" 5 | "github.com/speakeasy-api/speakeasy/internal/fs" 6 | "github.com/speakeasy-api/speakeasy/internal/log" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var languageServerCommand = &cobra.Command{ 11 | Use: "language-server", 12 | Short: "Runs Speakeasy's OpenAPI validator as a Language Server", 13 | Long: `Runs Speakeasy's OpenAPI validator as a Language Server, providing a fully compliant LSP backend for OpenAPI linting and validation.`, 14 | Hidden: true, 15 | } 16 | 17 | func languageServerInit(version string) { 18 | languageServerCommand.RunE = languageServerExec(version) 19 | rootCmd.AddCommand(languageServerCommand) 20 | } 21 | 22 | func languageServerExec(version string) func(cmd *cobra.Command, args []string) error { 23 | return func(cmd *cobra.Command, args []string) error { 24 | // setup logging to be discarded, it will invalidate the LSP protocol 25 | logger := log.NewNoop() 26 | 27 | fs := fs.NewFileSystem() 28 | 29 | return languageserver.NewServer(version, logger, fs).Run() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cmd/merge.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/speakeasy-api/speakeasy/internal/interactivity" 7 | "github.com/speakeasy-api/speakeasy/internal/log" 8 | "github.com/speakeasy-api/speakeasy/pkg/merge" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var mergeCmd = &cobra.Command{ 13 | Use: "merge", 14 | Short: "Merge multiple OpenAPI documents into a single document", 15 | Long: `Merge multiple OpenAPI documents into a single document, useful for merging multiple OpenAPI documents into a single document for generating a client SDK. 16 | Note: That any duplicate operations, components, etc. will be overwritten by the next document in the list.`, 17 | PreRunE: interactivity.GetMissingFlagsPreRun, 18 | RunE: mergeExec, 19 | } 20 | 21 | func mergeInit() { 22 | // TODO: Make the usage description change based on whether its being shown in interactive mode. Use a shared description for all array flags 23 | mergeCmd.Flags().StringArrayP("schemas", "s", []string{}, "a list of paths to OpenAPI documents to merge, specify -s `path/to/schema1.json` -s `path/to/schema2.json` etc") 24 | _ = mergeCmd.MarkFlagRequired("schemas") 25 | mergeCmd.Flags().StringP("out", "o", "", "path to the output file") 26 | _ = mergeCmd.MarkFlagRequired("out") 27 | mergeCmd.Flags().Bool("resolve", false, "resolve local references in the first schema file") 28 | 29 | rootCmd.AddCommand(mergeCmd) 30 | } 31 | 32 | func mergeExec(cmd *cobra.Command, args []string) error { 33 | inSchemas, err := cmd.Flags().GetStringArray("schemas") 34 | if err != nil { 35 | return err 36 | } 37 | 38 | outFile, err := cmd.Flags().GetString("out") 39 | if err != nil { 40 | return err 41 | } 42 | 43 | resolve, err := cmd.Flags().GetBool("resolve") 44 | if err != nil { 45 | return err 46 | } 47 | 48 | if resolve { 49 | dir := filepath.Dir(inSchemas[0]) 50 | if err := merge.MergeByResolvingLocalReferences(cmd.Context(), inSchemas[0], outFile, dir, "speakeasy-recommended", "", false); err != nil { 51 | return err 52 | } 53 | } else { 54 | if err := merge.MergeOpenAPIDocuments(cmd.Context(), inSchemas, outFile, "speakeasy-recommended", "", false); err != nil { 55 | return err 56 | } 57 | } 58 | 59 | log.From(cmd.Context()).Successf("Successfully merged %d schemas into %s", len(inSchemas), outFile) 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /cmd/migrate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/speakeasy-api/speakeasy/internal/migrate" 7 | "github.com/speakeasy-api/speakeasy/internal/model" 8 | "github.com/speakeasy-api/speakeasy/internal/model/flag" 9 | ) 10 | 11 | type MigrateFlags struct { 12 | Directory string `json:"directory"` 13 | } 14 | 15 | var migrateCmd = &model.ExecutableCommand[MigrateFlags]{ 16 | Usage: "migrate", 17 | Short: "migrate to v15 of the speakeasy workflow + action", 18 | Long: "migrate to v15 of the speakeasy workflow + action", 19 | Hidden: true, 20 | Run: migrateFunc, 21 | Flags: []flag.Flag{ 22 | flag.StringFlag{ 23 | Name: "directory", 24 | Shorthand: "d", 25 | Description: "directory to migrate. Expected to contain a `.github/workflows` directory. Defaults to `.`", 26 | DefaultValue: ".", 27 | }, 28 | }, 29 | } 30 | 31 | func migrateFunc(ctx context.Context, flags MigrateFlags) error { 32 | return migrate.Migrate(ctx, flags.Directory) 33 | } 34 | -------------------------------------------------------------------------------- /cmd/proxy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/speakeasy-api/speakeasy-proxy/pkg/proxy" 7 | "github.com/speakeasy-api/speakeasy/internal/auth" 8 | "github.com/speakeasy-api/speakeasy/internal/config" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var proxyCmd = &cobra.Command{ 13 | Use: "proxy", 14 | Short: "Proxy provides a reverse-proxy for debugging and testing Speakeasy's Traffic Capture capabilities", 15 | Long: `Proxy provides a reverse-proxy for debugging and testing Speakeasy's Traffic Capture capabilities`, 16 | RunE: proxyExec, 17 | Hidden: true, 18 | } 19 | 20 | func proxyInit() { 21 | proxyCmd.Flags().StringP("downstream", "d", "", "the downstream base url to proxy traffic to") 22 | proxyCmd.MarkFlagRequired("downstream") 23 | proxyCmd.Flags().StringP("api-id", "a", "", "the API ID to send captured traffic to") 24 | proxyCmd.MarkFlagRequired("api-id") 25 | proxyCmd.Flags().StringP("version-id", "v", "", "the Version ID to send captured traffic to") 26 | proxyCmd.MarkFlagRequired("version-id") 27 | proxyCmd.Flags().StringP("schema", "s", "", "path to an openapi document that can be used to match incoming traffic to API endpoints") 28 | proxyCmd.Flags().StringP("port", "p", "3333", "port to run the proxy on") 29 | 30 | rootCmd.AddCommand(proxyCmd) 31 | } 32 | 33 | func proxyExec(cmd *cobra.Command, args []string) error { 34 | authCtx, err := auth.Authenticate(cmd.Context(), false) 35 | if err != nil { 36 | return err 37 | } 38 | cmd.SetContext(authCtx) 39 | 40 | downstreamBaseURL, err := cmd.Flags().GetString("downstream") 41 | if err != nil { 42 | return err 43 | } 44 | 45 | apiID, err := cmd.Flags().GetString("api-id") 46 | if err != nil { 47 | return err 48 | } 49 | 50 | versionID, err := cmd.Flags().GetString("version-id") 51 | if err != nil { 52 | return err 53 | } 54 | 55 | port, err := cmd.Flags().GetString("port") 56 | if err != nil { 57 | return err 58 | } 59 | 60 | schema, err := cmd.Flags().GetString("schema") 61 | if err != nil { 62 | return err 63 | } 64 | 65 | var doc []byte 66 | 67 | if schema != "" { 68 | doc, err = os.ReadFile(schema) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | 74 | apiKey := config.GetSpeakeasyAPIKey() 75 | 76 | return proxy.StartProxy(proxy.Options{ 77 | DownstreamBaseURL: downstreamBaseURL, 78 | APIKey: apiKey, 79 | Port: port, 80 | ApiID: apiID, 81 | VersionID: versionID, 82 | OpenAPIDocument: doc, 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /cmd/update.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/speakeasy-api/speakeasy/internal/updates" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func updateInit(version, artifactArch string) { 11 | updateCmd := &cobra.Command{ 12 | Use: "update", 13 | Short: "Update the Speakeasy CLI to the latest version", 14 | Long: `Updates the Speakeasy CLI in-place to the latest version available by downloading from Github and replacing the current binary`, 15 | RunE: update(version, artifactArch), 16 | } 17 | 18 | updateCmd.Flags().IntP("timeout", "t", 60, "timeout in seconds for the update to complete") 19 | 20 | rootCmd.AddCommand(updateCmd) 21 | } 22 | 23 | func update(version, artifactArch string) func(cmd *cobra.Command, args []string) error { 24 | return func(cmd *cobra.Command, args []string) error { 25 | timeout, err := cmd.Flags().GetInt("timeout") 26 | if err != nil { 27 | return err 28 | } 29 | 30 | newVersion, err := updates.Update(cmd.Context(), version, artifactArch, timeout) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if newVersion == "" { 36 | fmt.Println("Already up to date") 37 | } else { 38 | fmt.Println("Updated to version", newVersion) 39 | } 40 | 41 | return nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /integration/main_test.go: -------------------------------------------------------------------------------- 1 | package integration_tests 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "runtime" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | // Entrypoint for CLI integration tests 13 | func TestMain(m *testing.M) { 14 | // Create a temporary directory 15 | if _, err := os.Stat(tempDir); err == nil { 16 | if err := os.RemoveAll(tempDir); err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | if err := os.Mkdir(tempDir, 0o755); err != nil { 22 | panic(err) 23 | } 24 | 25 | // Defer the removal of the temp directory 26 | defer func() { 27 | if err := os.RemoveAll(tempDir); err != nil { 28 | panic(err) 29 | } 30 | }() 31 | 32 | code := m.Run() 33 | os.Exit(code) 34 | } 35 | 36 | func setupTestDir(t *testing.T) string { 37 | t.Helper() 38 | _, filename, _, _ := runtime.Caller(0) 39 | workingDir := filepath.Dir(filename) 40 | temp, err := createTempDir(workingDir) 41 | assert.NoError(t, err) 42 | registerCleanup(t, workingDir, temp) 43 | 44 | return temp 45 | } 46 | 47 | func registerCleanup(t *testing.T, workingDir string, temp string) { 48 | t.Helper() 49 | t.Cleanup(func() { 50 | os.RemoveAll(filepath.Join(workingDir, temp)) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /integration/overlay_test.go: -------------------------------------------------------------------------------- 1 | package integration_tests 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestOverlayMatchesSnapshot(t *testing.T) { 13 | t.Parallel() 14 | _, filename, _, _ := runtime.Caller(0) 15 | overlayFolder := filepath.Join(filepath.Dir(filename), "resources", "overlay") 16 | overlayFilePath, err := filepath.Abs(filepath.Join(overlayFolder, "overlay.yaml")) 17 | require.NoError(t, err) 18 | schemaFilePath, err := filepath.Abs(filepath.Join(overlayFolder, "openapi.yaml")) 19 | require.NoError(t, err) 20 | expectedBytes, err := os.ReadFile(filepath.Join(overlayFolder, "openapi-overlayed-expected.yaml")) 21 | require.NoError(t, err) 22 | 23 | temp := setupTestDir(t) 24 | outputPath, err := filepath.Abs(filepath.Join(temp, "output.yaml")) 25 | require.NoError(t, err) 26 | 27 | args := []string{"overlay", "apply", "--schema", schemaFilePath, "--overlay", overlayFilePath, "--out", outputPath} 28 | cmdErr := execute(t, temp, args...).Run() 29 | require.NoError(t, cmdErr) 30 | readBytes, err := os.ReadFile(outputPath) 31 | require.NoError(t, err) 32 | 33 | // Normalize line endings for both expected and actual content 34 | expectedNormalized := normalizeLineEndings(string(expectedBytes)) 35 | actualNormalized := normalizeLineEndings(string(readBytes)) 36 | 37 | require.Equal(t, expectedNormalized, actualNormalized) 38 | } 39 | 40 | // normalizeLineEndings replaces all occurrences of \r\n with \n 41 | func normalizeLineEndings(s string) string { 42 | return strings.ReplaceAll(s, "\r\n", "\n") 43 | } 44 | -------------------------------------------------------------------------------- /integration/resources/codeSamples-JSON.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: CodeSamples overlay for go target 4 | version: 0.0.0 5 | actions: 6 | - target: $["paths"]["/pets"]["get"] 7 | update: 8 | "x-codeSamples": 9 | - "lang": "go" 10 | "label": "listPets" 11 | "source": |- 12 | package main 13 | 14 | import( 15 | "openapi" 16 | "context" 17 | "log" 18 | ) 19 | 20 | func main() { 21 | s := openapi.New() 22 | 23 | 24 | var limit *int = openapi.Int(21453) 25 | 26 | ctx := context.Background() 27 | res, err := s.ListPets(ctx, limit) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | if res.Pets != nil { 32 | // handle response 33 | } 34 | } 35 | - target: $["paths"]["/pets"]["post"] 36 | update: 37 | "x-codeSamples": 38 | - "lang": "go" 39 | "label": "createPets" 40 | "source": |- 41 | package main 42 | 43 | import( 44 | "openapi" 45 | "context" 46 | "openapi/models/components" 47 | "log" 48 | ) 49 | 50 | func main() { 51 | s := openapi.New() 52 | 53 | ctx := context.Background() 54 | res, err := s.CreatePets(ctx, components.Pet{ 55 | ID: 596804, 56 | Name: "", 57 | }) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | if res != nil { 62 | // handle response 63 | } 64 | } 65 | - target: $["paths"]["/pets/{petId}"]["get"] 66 | update: 67 | "x-codeSamples": 68 | - "lang": "go" 69 | "label": "showPetById" 70 | "source": |- 71 | package main 72 | 73 | import( 74 | "openapi" 75 | "context" 76 | "log" 77 | ) 78 | 79 | func main() { 80 | s := openapi.New() 81 | 82 | 83 | var petID string = "" 84 | 85 | ctx := context.Background() 86 | res, err := s.ShowPetByID(ctx, petID) 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | if res.Pet != nil { 91 | // handle response 92 | } 93 | } -------------------------------------------------------------------------------- /integration/resources/normalize-input.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: Normalize-Test 4 | version: 1.0.0 5 | paths: 6 | /test: 7 | get: 8 | responses: 9 | "200": 10 | content: 11 | application/json: 12 | schema: 13 | prefixItems: 14 | - type: string 15 | - type: string 16 | minItems: 1 17 | maxItems: 1 18 | type: array 19 | components: 20 | schemas: 21 | test: 22 | prefixItems: 23 | - type: string 24 | - type: string 25 | minItems: 1 26 | maxItems: 1 27 | type: array 28 | -------------------------------------------------------------------------------- /integration/resources/normalize-output.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: Normalize-Test 4 | version: 1.0.0 5 | paths: 6 | /test: 7 | get: 8 | responses: 9 | "200": 10 | content: 11 | application/json: 12 | schema: 13 | type: string 14 | components: 15 | schemas: 16 | test: 17 | type: string -------------------------------------------------------------------------------- /integration/resources/overlay/overlay.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: Drinks Overlay 4 | version: 1.2.3 5 | x-info-extension: 42 6 | actions: 7 | - target: $.paths["/drink/{name}"].get 8 | description: Test update 9 | update: 10 | parameters: 11 | - x-parameter-extension: foo 12 | name: test 13 | description: Test parameter 14 | in: query 15 | schema: 16 | type: string 17 | responses: 18 | '200': 19 | x-response-extension: foo 20 | description: Test response 21 | content: 22 | application/json: 23 | schema: 24 | type: string 25 | x-action-extension: foo 26 | - target: $.paths["/drinks"].get 27 | description: Test remove 28 | remove: true 29 | x-action-extension: bar 30 | - target: $.paths["/drinks"] 31 | update: 32 | x-speakeasy-note: 33 | "$ref": "./removeNote.yaml" 34 | - target: $.tags 35 | update: 36 | - name: Testing 37 | description: just a description 38 | - target: $.paths["/anything/selectGlobalServer"]["x-my-ignore"] 39 | update: 40 | servers: 41 | - url: http://localhost:35123 42 | description: The default server. 43 | - target: $.paths["/drink/{name}"].get 44 | update: 45 | description: | 46 | A long description 47 | to validate that we handle indentation properly 48 | 49 | With a second paragraph 50 | x-top-level-extension: true 51 | -------------------------------------------------------------------------------- /integration/resources/renameOperationOverlay.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: Change operationId for findByTags 4 | version: 0.0.0 5 | actions: 6 | - target: $["paths"]["/pet/findByTags"]["get"]["operationId"] 7 | update: findByTagsNew 8 | - target: $["paths"]["/pet/findByTags"]["get"] 9 | update: 10 | x-codeSamples: 11 | - lang: go 12 | label: updatePet 13 | source: |- 14 | package main -------------------------------------------------------------------------------- /integration/utils.go: -------------------------------------------------------------------------------- 1 | package integration_tests 2 | 3 | import ( 4 | "io" 5 | "math/rand" 6 | "net/url" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "testing" 11 | ) 12 | 13 | const ( 14 | tempDir = "temp" 15 | letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 16 | version = "0.0.1" 17 | artifactArch = "linux_amd64" 18 | ) 19 | 20 | func createTempDir(wd string) (string, error) { 21 | target := filepath.Join(wd, tempDir, randStringBytes(7)) 22 | if err := os.Mkdir(target, 0o755); err != nil { 23 | return "", err 24 | } 25 | 26 | return target, nil 27 | } 28 | 29 | func isLocalFileReference(filePath string) bool { 30 | u, err := url.Parse(filePath) 31 | if err != nil { 32 | return true 33 | } 34 | 35 | return u.Scheme == "" || u.Scheme == "file" 36 | } 37 | 38 | func copyFile(src, dst string) error { 39 | _, filename, _, _ := runtime.Caller(0) 40 | targetSrc := filepath.Join(filepath.Dir(filename), src) 41 | 42 | sourceFile, err := os.Open(targetSrc) 43 | if err != nil { 44 | return err 45 | } 46 | defer sourceFile.Close() 47 | 48 | destinationFile, err := os.Create(dst) 49 | if err != nil { 50 | return err 51 | } 52 | defer destinationFile.Close() 53 | 54 | _, err = io.Copy(destinationFile, sourceFile) 55 | return err 56 | } 57 | 58 | var randStringBytes = func(n int) string { 59 | b := make([]byte, n) 60 | for i := range b { 61 | b[i] = letterBytes[rand.Intn(len(letterBytes))] 62 | } 63 | return string(b) 64 | } 65 | 66 | func expectedFilesByLanguage(language string) []string { 67 | switch language { 68 | case "go": 69 | return []string{"README.md", "sdk.go", "go.mod"} 70 | case "mcp-typescript", "typescript": 71 | return []string{"README.md", "package.json", "tsconfig.json"} 72 | case "python": 73 | return []string{"README.md", "setup.py"} 74 | default: 75 | return []string{} 76 | } 77 | } 78 | 79 | func checkForExpectedFiles(t *testing.T, outdir string, files []string) { 80 | for _, fileName := range files { 81 | filePath := filepath.Join(outdir, fileName) 82 | fileInfo, err := os.Stat(filePath) 83 | if err != nil { 84 | t.Errorf("Error checking file %s in directory %s: %v", fileName, outdir, err) 85 | continue 86 | } 87 | 88 | if fileInfo.Size() == 0 { 89 | t.Errorf("Expected file %s in directory %s is empty.", fileName, outdir) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /internal/arazzo/arazzo.go: -------------------------------------------------------------------------------- 1 | package arazzo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/charmbracelet/lipgloss" 9 | "github.com/speakeasy-api/openapi/arazzo" 10 | "github.com/speakeasy-api/speakeasy/internal/charm/styles" 11 | "github.com/speakeasy-api/speakeasy/internal/log" 12 | ) 13 | 14 | func Validate(ctx context.Context, file string) error { 15 | logger := log.From(ctx) 16 | logger.Info("Validating Arazzo document...\n") 17 | 18 | f, err := os.Open(file) 19 | if err != nil { 20 | return fmt.Errorf("failed to open file: %w", err) 21 | } 22 | defer f.Close() 23 | 24 | _, validationErrors, err := arazzo.Unmarshal(ctx, f) 25 | if err != nil { 26 | return fmt.Errorf("failed to unmarshal file: %w", err) 27 | } 28 | 29 | if len(validationErrors) == 0 { 30 | msg := styles.RenderSuccessMessage( 31 | "Arazzo document valid ✓", 32 | "0 errors", 33 | ) 34 | logger.Println(msg) 35 | return nil 36 | } 37 | 38 | lines := make([]string, len(validationErrors)) 39 | 40 | for _, err := range validationErrors { 41 | lines = append(lines, fmt.Sprintf("- %s", err.Error())) 42 | } 43 | 44 | msg := styles.RenderErrorMessage("Validation Errors", lipgloss.Center, lines...) 45 | logger.Println(msg) 46 | 47 | return fmt.Errorf(`Arazzo document invalid ✖`) 48 | } 49 | -------------------------------------------------------------------------------- /internal/changes/openapiChanges.go: -------------------------------------------------------------------------------- 1 | package changes 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "os" 7 | "time" 8 | 9 | changesModel "github.com/pb33f/openapi-changes/model" 10 | "github.com/speakeasy-api/speakeasy-core/changes" 11 | html_report "github.com/speakeasy-api/speakeasy-core/changes/html-report" 12 | ) 13 | 14 | type Changes []*changesModel.Commit 15 | type VersionBump string 16 | 17 | type Summary struct { 18 | Bump VersionBump 19 | Text string 20 | Table [][]string 21 | } 22 | 23 | var ( 24 | Major VersionBump = "major" 25 | Minor VersionBump = "minor" 26 | Patch VersionBump = "patch" 27 | None VersionBump = "none" 28 | ) 29 | 30 | func GetChanges(ctx context.Context, oldLocation, newLocation string) (Changes, error) { 31 | c, errs := changes.GetChanges(ctx, oldLocation, newLocation, changes.SummaryOptions{}) 32 | return c, errors.Join(errs...) 33 | } 34 | 35 | func (c Changes) GetHTMLReport() []byte { 36 | generator := html_report.NewHTMLReport(false, time.Now(), c) 37 | return generator.GenerateReport(false) 38 | } 39 | 40 | func (c Changes) WriteHTMLReport(out string) error { 41 | return os.WriteFile(out, c.GetHTMLReport(), 0o644) 42 | } 43 | 44 | func (c Changes) GetSummary() (*Summary, error) { 45 | text, table, _, err := changes.GetSummaryDetails(c) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | bump := None 51 | 52 | // NB: for now, we just bump patch when we see any changes to the document 53 | if len(table) > 0 { 54 | bump = Patch 55 | } 56 | 57 | return &Summary{ 58 | Bump: bump, 59 | Text: text, 60 | Table: table, 61 | }, nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/charm/formTheme.go: -------------------------------------------------------------------------------- 1 | package charm 2 | 3 | import ( 4 | "github.com/charmbracelet/lipgloss" 5 | "github.com/speakeasy-api/huh" 6 | "github.com/speakeasy-api/speakeasy/internal/charm/styles" 7 | ) 8 | 9 | var formTheme *huh.Theme 10 | 11 | func init() { 12 | t := *huh.ThemeBase() 13 | 14 | t.Focused.Base = t.Focused.Base.BorderForeground(styles.Focused.GetForeground()) 15 | t.Focused.Title = t.Focused.Title.Foreground(styles.Focused.GetForeground()).Bold(true) 16 | t.Focused.Description = t.Focused.Description.Foreground(styles.Dimmed.GetForeground()).Italic(true).Inline(false) 17 | t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(styles.Colors.Red) 18 | t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(styles.Colors.Red) 19 | t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(styles.Focused.GetForeground()).Bold(true) 20 | t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(styles.Focused.GetForeground()).Bold(true) 21 | t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(styles.Focused.GetForeground()).Bold(true) 22 | t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(styles.Focused.GetForeground()).SetString("✓ ").Bold(true) 23 | t.Focused.UnselectedPrefix = lipgloss.NewStyle().SetString("") 24 | t.Focused.FocusedButton = t.Focused.FocusedButton.Background(styles.Colors.Green) 25 | t.Focused.BlurredButton = t.Focused.BlurredButton.Background(styles.Dimmed.GetForeground()) 26 | t.Focused.Next = t.Focused.FocusedButton 27 | 28 | t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(styles.Colors.WhiteBlackAdaptive) 29 | t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(styles.Dimmed.GetForeground()).Italic(true) 30 | t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(styles.Colors.WhiteBlackAdaptive) 31 | t.Focused.TextInput.Text = t.Focused.TextInput.Text.Foreground(styles.Colors.WhiteBlackAdaptive) 32 | 33 | t.Blurred.Description = t.Focused.Description 34 | t.Blurred.TextInput.Placeholder = t.Blurred.TextInput.Placeholder.Italic(true) 35 | t.Blurred.Title = t.Blurred.Title.Foreground(styles.FocusedDimmed.GetForeground()) 36 | t.Blurred.TextInput.Text = t.Blurred.TextInput.Text.Foreground(styles.FocusedDimmed.GetForeground()) 37 | t.Blurred.SelectedOption = t.Blurred.SelectedOption.Foreground(styles.FocusedDimmed.GetForeground()) 38 | t.Blurred.SelectSelector = t.Blurred.SelectSelector.Foreground(styles.FocusedDimmed.GetForeground()) 39 | t.Blurred.SelectedPrefix = lipgloss.NewStyle().Foreground(styles.FocusedDimmed.GetForeground()).SetString("✓ ") 40 | t.Blurred.UnselectedPrefix = lipgloss.NewStyle().SetString("") 41 | 42 | formTheme = &t 43 | } 44 | -------------------------------------------------------------------------------- /internal/charm/internalModel.go: -------------------------------------------------------------------------------- 1 | package charm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | tea "github.com/charmbracelet/bubbletea" 8 | ) 9 | 10 | type InternalModel interface { 11 | Init() tea.Cmd 12 | Update(msg tea.Msg) (tea.Model, tea.Cmd) 13 | HandleKeypress(key string) tea.Cmd // A convenience method for handling keypresses. Should usually return nil. 14 | View() string 15 | SetWidth(width int) 16 | OnUserExit() 17 | } 18 | 19 | type modelWrapper struct { 20 | model InternalModel 21 | signalExit bool 22 | } 23 | 24 | func (m modelWrapper) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 25 | switch msg := msg.(type) { 26 | case tea.KeyMsg: 27 | switch keypress := msg.String(); keypress { 28 | case "ctrl+c", "esc": 29 | m.model.OnUserExit() 30 | m.signalExit = true 31 | return m, tea.Quit 32 | default: 33 | if cmd := m.model.HandleKeypress(keypress); cmd != nil { 34 | return m, cmd 35 | } 36 | } 37 | case tea.WindowSizeMsg: 38 | m.model.SetWidth(msg.Width) 39 | } 40 | 41 | _, cmd := m.model.Update(msg) 42 | return m, cmd 43 | } 44 | 45 | func (m modelWrapper) View() string { 46 | return m.model.View() 47 | } 48 | 49 | func (m modelWrapper) Init() tea.Cmd { 50 | return m.model.Init() 51 | } 52 | 53 | func RunModel(m InternalModel, opts ...tea.ProgramOption) (InternalModel, error) { 54 | model := modelWrapper{ 55 | model: m, 56 | } 57 | if mResult, err := tea.NewProgram(model, opts...).Run(); err != nil { 58 | return nil, err 59 | } else { 60 | if m, ok := mResult.(modelWrapper); ok { 61 | if m.signalExit { 62 | os.Exit(0) 63 | } 64 | 65 | return m.model, nil 66 | } 67 | 68 | fmt.Println(mResult) 69 | } 70 | 71 | return nil, nil 72 | } 73 | 74 | var _ tea.Model = modelWrapper{} 75 | -------------------------------------------------------------------------------- /internal/charm/styles/markdown.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "fmt" 5 | "github.com/charmbracelet/lipgloss" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | patternToStyle = map[string]lipgloss.Style{ 12 | "\\*": lipgloss.NewStyle().Bold(true), 13 | "\\^": lipgloss.NewStyle().Italic(true), //Can't be underscore because values like SPEAKEASY_API_KEY get messed up 14 | "`": HeavilyEmphasized, 15 | } 16 | ) 17 | 18 | // InjectMarkdownStyles parses the string for markdown patterns and applies the appropriate styles 19 | // For example, the string "*bold text*" will be rendered in bold with the asterisks stripped out 20 | // The "parent style" will then be resumed, which is the nearest preceding style to the match. 21 | func InjectMarkdownStyles(s string) string { 22 | parentStyleRegex := regexp.MustCompile(`\x1b\[.*?m`) 23 | parentStyle := "\x1b[0m" 24 | 25 | for pattern, style := range patternToStyle { 26 | s2 := strings.Builder{} 27 | lastEndingIndex := 0 28 | 29 | codeRx := regexp.MustCompile(fmt.Sprintf("%s([^%s]+)%s", pattern, pattern, pattern)) 30 | 31 | for _, match := range codeRx.FindAllStringSubmatchIndex(s, -1) { 32 | // Find the last style present in the string before the match 33 | before := s[lastEndingIndex : match[2]-1] // Get the portion of the string before the match, up to the symbol 34 | parentStyleMatches := parentStyleRegex.FindAllString(before, -1) 35 | if len(parentStyleMatches) > 0 { 36 | parentStyle = parentStyleMatches[len(parentStyleMatches)-1] 37 | } 38 | 39 | s2.WriteString(before) 40 | s2.WriteString(style.Render(s[match[2]:match[3]])) 41 | s2.WriteString(parentStyle) 42 | lastEndingIndex = match[3] + 1 // +1 because of closing symbol 43 | } 44 | 45 | s = s2.String() + s[lastEndingIndex:] 46 | } 47 | 48 | return s 49 | } 50 | -------------------------------------------------------------------------------- /internal/charm/transformAccessor.go: -------------------------------------------------------------------------------- 1 | package charm 2 | 3 | import ( 4 | "github.com/charmbracelet/huh" 5 | ) 6 | 7 | type TransformAccessor struct { 8 | value *string 9 | transform func(string) string 10 | } 11 | 12 | func (t *TransformAccessor) Get() string { 13 | if t.value == nil { 14 | return "" 15 | } 16 | return *t.value 17 | } 18 | 19 | func (t *TransformAccessor) Set(value string) { 20 | value = t.transform(value) 21 | *t.value = value 22 | } 23 | 24 | var _ huh.Accessor[string] = &TransformAccessor{} 25 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /internal/defaultcodesamples/defaultcodesamples.go: -------------------------------------------------------------------------------- 1 | package defaultcodesamples 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | ) 10 | 11 | type DefaultCodeSamplesFlags struct { 12 | SchemaPath string `json:"schema"` 13 | Language string `json:"language"` 14 | Out string `json:"out"` 15 | } 16 | 17 | //go:embed out/defaultcodesamples.js 18 | var javascriptFile embed.FS 19 | 20 | func DefaultCodeSamples(ctx context.Context, flags DefaultCodeSamplesFlags) error { 21 | // Ensure the user has node installed 22 | nodeBinary, err := findNodeBinary() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | out, err := os.Create(flags.Out) 28 | if err != nil { 29 | return fmt.Errorf("failed to open output file: %w", err) 30 | } 31 | defer out.Close() 32 | 33 | // Copy the file to a temp location 34 | result, err := javascriptFile.ReadFile("out/defaultcodesamples.js") 35 | if err != nil { 36 | return fmt.Errorf("failed to read default code samples file: %w", err) 37 | } 38 | tempDir := os.TempDir() 39 | tempFile := fmt.Sprintf("%s/defaultcodesamples.js", tempDir) 40 | err = os.WriteFile(tempFile, result, 0o644) 41 | if err != nil { 42 | return fmt.Errorf("failed to write default code samples file: %w", err) 43 | } 44 | 45 | cmd := exec.Command( 46 | nodeBinary, 47 | tempFile, 48 | "-s", flags.SchemaPath, 49 | "-l", flags.Language, 50 | ) 51 | 52 | cmd.Stdout = out 53 | cmd.Stderr = os.Stderr 54 | 55 | err = cmd.Run() 56 | if err != nil { 57 | return fmt.Errorf("failed to run command: %w", err) 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func findNodeBinary() (string, error) { 64 | // Check if node is installed 65 | _, err := exec.Command("node", "--version").Output() 66 | if err == nil { 67 | return "node", nil 68 | } 69 | 70 | return "", fmt.Errorf("node is required to run this command. Please install node and try again") 71 | } 72 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "defaultcodesamples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx esbuild ./src/main.ts --bundle --platform=node --outfile=out/defaultcodesamples.js --main-fields=module,main", 8 | "start:petstore": "npm run build && node out/defaultcodesamples.js -s https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml -l shell ", 9 | "test": "vitest", 10 | "postinstall": "patch-package" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@readme/oas-to-har": "^23.2.18", 17 | "@readme/oas-to-snippet": "^25.2.11", 18 | "@readme/openapi-parser": "^2.6.0", 19 | "@readme/postman-to-openapi": "^4.1.0", 20 | "command-line-args": "^5.2.1", 21 | "oas": "^24.5.0", 22 | "patch-package": "^8.0.0", 23 | "swagger2openapi": "^7.0.8", 24 | "yaml": "^2.4.5" 25 | }, 26 | "devDependencies": { 27 | "@types/command-line-args": "^5.2.3", 28 | "@types/node": "^20.14.9", 29 | "esbuild": "0.21.5", 30 | "typescript": "^5.5.2", 31 | "vitest": "^1.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/patches/@jsdevtools+ono+7.1.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@jsdevtools/ono/esm/index.js b/node_modules/@jsdevtools/ono/esm/index.js 2 | index 7e6b469..91685fe 100644 3 | --- a/node_modules/@jsdevtools/ono/esm/index.js 4 | +++ b/node_modules/@jsdevtools/ono/esm/index.js 5 | @@ -5,7 +5,7 @@ export * from "./types"; 6 | export { ono }; 7 | export default ono; 8 | // CommonJS default export hack 9 | -if (typeof module === "object" && typeof module.exports === "object") { 10 | +if (typeof module === "object" && typeof module.exports === "object" && typeof module.exports.default == "object") { 11 | module.exports = Object.assign(module.exports.default, module.exports); 12 | } 13 | //# sourceMappingURL=index.js.map 14 | \ No newline at end of file 15 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/src/main.ts: -------------------------------------------------------------------------------- 1 | import commandLineArgs from "command-line-args"; 2 | import { 3 | SupportedTargets, 4 | getSupportedLanguages, 5 | } from "@readme/oas-to-snippet/languages"; 6 | import { 7 | readFilePathOrUrl, 8 | generateCodeSamplesOverlay, 9 | } from "./generateCodeSamplesOverlay"; 10 | 11 | async function main() { 12 | const options = commandLineArgs([ 13 | { name: "schema", alias: "s", type: String }, 14 | { name: "language", alias: "l", type: String }, 15 | ]); 16 | const filePathOrUrl = options.schema; 17 | const language = options.language; 18 | 19 | if (!filePathOrUrl) { 20 | throw new Error("Please provide a schema file"); 21 | } 22 | 23 | if (!language || typeof language !== "string") { 24 | throw new Error("Please provide a language"); 25 | } 26 | 27 | const supportedLanguages = getSupportedLanguages(); 28 | 29 | const isSupportedLanguage = (x: string): x is SupportedTargets => 30 | x in supportedLanguages; 31 | 32 | if (!isSupportedLanguage(language)) { 33 | throw new Error( 34 | `Language not supported: ${language}. Supported languages: ${Object.keys( 35 | supportedLanguages 36 | ).join(", ")}` 37 | ); 38 | } 39 | 40 | // Read the file 41 | const fileContent = await readFilePathOrUrl(filePathOrUrl); 42 | 43 | const { overlay, errors } = await generateCodeSamplesOverlay( 44 | fileContent, 45 | language 46 | ); 47 | 48 | process.stdout.write(JSON.stringify(overlay, null, 2)); 49 | for (const error of errors || []) { 50 | process.stderr.write( 51 | `Error generating code sample for ${error.jsonPathSelector}: ${error.error}` 52 | ); 53 | } 54 | } 55 | 56 | main().catch((err) => { 57 | console.error(err); 58 | process.exit(1); 59 | }); 60 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/src/oas-normalize/.sink.d.ts: -------------------------------------------------------------------------------- 1 | // These packages don't have any TS types so we need to declare a module in order to use them. 2 | declare module 'swagger2openapi'; 3 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/src/oas-normalize/lib/types.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | colorizeErrors?: boolean; 3 | enablePaths?: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/src/oas-normalize/readme.md: -------------------------------------------------------------------------------- 1 | # Why is this here? 2 | 3 | Taken from https://github.com/readmeio/oas-normalize 4 | 5 | Sub dependency of oas-normalize is jsonc-parser which has the following issue: 6 | https://github.com/microsoft/node-jsonc-parser/issues/57 7 | -------------------------------------------------------------------------------- /internal/defaultcodesamples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | }, 23 | "include": ["src/**/*.ts"], 24 | "files": ["./src/oas-normalize/.sink.d.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /internal/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import "os" 4 | 5 | // Returns true if the CLI_RUNTIME environment variable is set to "docs". 6 | // This environment variable is used to determine when website documentation 7 | // is being rendered to prevent unexpected CLI formatting characters. 8 | func IsDocsRuntime() bool { 9 | return os.Getenv("CLI_RUNTIME") == "docs" 10 | } 11 | 12 | func IsGithubAction() bool { 13 | return os.Getenv("GITHUB_ACTIONS") == "true" 14 | } 15 | 16 | func IsGithubDebugMode() bool { 17 | return os.Getenv("RUNNER_DEBUG") == "true" 18 | } 19 | 20 | func PinnedVersion() string { 21 | return os.Getenv("PINNED_VERSION") 22 | } 23 | 24 | func GoArch() string { 25 | return os.Getenv("GOARCH") 26 | } 27 | 28 | func IsLocalDev() bool { 29 | return os.Getenv("SPEAKEASY_ENVIRONMENT") == "local" 30 | } 31 | 32 | // Returns the SPEAKEASY_RUN_LOCATION environment variable value. For example, 33 | // this is set by Speakeasy maintained GitHub Actions to "action". 34 | func SpeakeasyRunLocation() string { 35 | return os.Getenv("SPEAKEASY_RUN_LOCATION") 36 | } 37 | -------------------------------------------------------------------------------- /internal/fs/fs.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | 7 | "github.com/speakeasy-api/openapi-generation/v2/pkg/filesystem" 8 | ) 9 | 10 | // FileSystem is a wrapper around the os.FS type that implements the filesystem.FileSystem interface needed by the openapi-generation package 11 | type FileSystem struct { 12 | } 13 | 14 | var _ filesystem.FileSystem = &FileSystem{} 15 | 16 | func NewFileSystem() *FileSystem { 17 | return &FileSystem{} 18 | } 19 | 20 | func (f *FileSystem) ReadFile(path string) ([]byte, error) { 21 | return os.ReadFile(path) 22 | } 23 | 24 | func (f *FileSystem) WriteFile(path string, data []byte, perm os.FileMode) error { 25 | return os.WriteFile(path, data, perm) 26 | } 27 | 28 | func (f *FileSystem) MkdirAll(path string, perm os.FileMode) error { 29 | return os.MkdirAll(path, perm) 30 | } 31 | 32 | func (f *FileSystem) Open(path string) (fs.File, error) { 33 | return os.Open(path) 34 | } 35 | 36 | func (f *FileSystem) OpenFile(path string, flag int, perm os.FileMode) (filesystem.File, error) { 37 | return os.OpenFile(path, flag, perm) 38 | } 39 | 40 | func (f *FileSystem) Stat(path string) (os.FileInfo, error) { 41 | return os.Stat(path) 42 | } 43 | 44 | func (f *FileSystem) Remove(path string) error { 45 | return os.Remove(path) 46 | } 47 | 48 | func (f *FileSystem) RemoveAll(path string) error { 49 | return os.RemoveAll(path) 50 | } 51 | -------------------------------------------------------------------------------- /internal/git/repository.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | gitc "github.com/go-git/go-git/v5" 8 | "github.com/go-git/go-git/v5/config" 9 | "github.com/go-git/go-git/v5/plumbing" 10 | ) 11 | 12 | type Repository struct { 13 | repo *gitc.Repository 14 | } 15 | 16 | // NewLocalRepository will attempt to open a pre-existing git repository in the given directory 17 | // If no repository is found, it will return an empty Repository 18 | func NewLocalRepository(dir string) (*Repository, error) { 19 | repo, err := gitc.PlainOpenWithOptions(dir, &gitc.PlainOpenOptions{ 20 | DetectDotGit: true, 21 | }) 22 | if errors.Is(err, gitc.ErrRepositoryNotExists) { 23 | return &Repository{}, nil 24 | } else if err != nil { 25 | return &Repository{}, fmt.Errorf("git: %w", err) 26 | } 27 | 28 | return &Repository{repo: repo}, nil 29 | } 30 | 31 | // InitLocalRepository will initialize a new git repository in the given directory 32 | func InitLocalRepository(dir string) (*Repository, error) { 33 | // Try to retrieve the default branch from the global git config 34 | // if the user has an explicit default branch set. Otherwise it 35 | // will default to master. 36 | branch := getDefaultGitBranch() 37 | reference := plumbing.NewBranchReferenceName(branch) 38 | 39 | repo, err := gitc.PlainInitWithOptions(dir, &gitc.PlainInitOptions{ 40 | Bare: false, 41 | InitOptions: gitc.InitOptions{ 42 | DefaultBranch: reference, 43 | }, 44 | }) 45 | 46 | if err != nil { 47 | return &Repository{}, err 48 | } 49 | 50 | return &Repository{repo: repo}, nil 51 | } 52 | 53 | func (r *Repository) IsNil() bool { 54 | return r.repo == nil 55 | } 56 | 57 | func (r *Repository) HeadHash() (string, error) { 58 | if r.IsNil() { 59 | return "", nil 60 | } 61 | 62 | head, err := r.repo.Head() 63 | if errors.Is(err, plumbing.ErrReferenceNotFound) { 64 | return "", nil 65 | } else if err != nil { 66 | return "", fmt.Errorf("git: %w", err) 67 | } 68 | 69 | return head.Hash().String(), nil 70 | } 71 | 72 | const ( 73 | defaultBranch string = "main" 74 | ) 75 | 76 | // Retrieves the default branch from the user's global git config 77 | // e.g 78 | // git config --get init.defaultbranch 79 | // To set: 80 | // git config --global init.defaultbranch main 81 | func getDefaultGitBranch() string { 82 | if cfg, _ := config.LoadConfig(config.GlobalScope); cfg != nil { 83 | if branch := cfg.Init.DefaultBranch; branch != "" { 84 | return branch 85 | } 86 | } 87 | return defaultBranch 88 | } 89 | -------------------------------------------------------------------------------- /internal/github/github_test.go: -------------------------------------------------------------------------------- 1 | package github_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/speakeasy-api/openapi-generation/v2/pkg/errors" 8 | "github.com/speakeasy-api/speakeasy/internal/github" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSortErrors(t *testing.T) { 13 | type args struct { 14 | errs []error 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | want []error 20 | }{ 21 | { 22 | name: "Sort errors by severity and line number", 23 | args: args{ 24 | errs: []error{ 25 | fmt.Errorf("random error"), 26 | errors.NewValidationError("error 2", 10, fmt.Errorf("validation error 2")), 27 | errors.NewValidationWarning("warning 1", 2, fmt.Errorf("validation warning 1")), 28 | errors.NewValidationWarning("warning 2", 3, fmt.Errorf("validation warning 2")), 29 | errors.NewUnsupportedError("unsupported 2", 5), 30 | errors.NewUnsupportedError("unsupported 1", 4), 31 | &errors.ValidationError{ 32 | Severity: errors.SeverityHint, 33 | Message: "hint 2", 34 | LineNumber: 7, 35 | Cause: fmt.Errorf("validation hint 2"), 36 | }, 37 | &errors.ValidationError{ 38 | Severity: errors.SeverityHint, 39 | Message: "hint 1", 40 | LineNumber: 6, 41 | Cause: fmt.Errorf("validation hint 1"), 42 | }, 43 | errors.NewValidationError("error 1", 1, fmt.Errorf("validation error 1")), 44 | }, 45 | }, 46 | want: []error{ 47 | errors.NewValidationError("error 1", 1, fmt.Errorf("validation error 1")), 48 | errors.NewValidationError("error 2", 10, fmt.Errorf("validation error 2")), 49 | errors.NewValidationWarning("warning 1", 2, fmt.Errorf("validation warning 1")), 50 | errors.NewValidationWarning("warning 2", 3, fmt.Errorf("validation warning 2")), 51 | &errors.ValidationError{ 52 | Severity: errors.SeverityHint, 53 | Message: "hint 1", 54 | LineNumber: 6, 55 | Cause: fmt.Errorf("validation hint 1"), 56 | }, 57 | &errors.ValidationError{ 58 | Severity: errors.SeverityHint, 59 | Message: "hint 2", 60 | LineNumber: 7, 61 | Cause: fmt.Errorf("validation hint 2"), 62 | }, 63 | errors.NewUnsupportedError("unsupported 1", 4), 64 | errors.NewUnsupportedError("unsupported 2", 5), 65 | fmt.Errorf("random error"), 66 | }, 67 | }, 68 | } 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | github.SortErrors(tt.args.errs) 72 | 73 | assert.Equal(t, tt.want, tt.args.errs) 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/interactivity/hiddenCommands.go: -------------------------------------------------------------------------------- 1 | package interactivity 2 | 3 | // CommandsHiddenFromInteractivity is a list of commands that should not be shown in the interactive menu 4 | // If the command should also be hidden from --help and the generated docs, set Hidden: true instead 5 | var CommandsHiddenFromInteractivity = []string{ 6 | "auth", 7 | "update", 8 | "suggest", 9 | "completion", 10 | "help", 11 | "migrate", 12 | "merge", 13 | "bump", 14 | "tag", 15 | "clean", 16 | "ask", 17 | } 18 | -------------------------------------------------------------------------------- /internal/interactivity/simpleConfirm.go: -------------------------------------------------------------------------------- 1 | package interactivity 2 | 3 | import ( 4 | "github.com/speakeasy-api/huh" 5 | charm_internal "github.com/speakeasy-api/speakeasy/internal/charm" 6 | ) 7 | 8 | func SimpleConfirm(message string, defaultValue bool) bool { 9 | confirm := defaultValue 10 | 11 | if _, err := charm_internal.NewForm( 12 | huh.NewForm(charm_internal.NewBranchPrompt(message, "", &confirm)), 13 | ). 14 | ExecuteForm(); err != nil { 15 | return false 16 | } 17 | 18 | return confirm 19 | } 20 | -------------------------------------------------------------------------------- /internal/interactivity/spinner.go: -------------------------------------------------------------------------------- 1 | package interactivity 2 | 3 | // A simple program demonstrating the spinner component from the Bubbles 4 | // component library. 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/charmbracelet/bubbles/spinner" 10 | tea "github.com/charmbracelet/bubbletea" 11 | "github.com/charmbracelet/lipgloss" 12 | "github.com/speakeasy-api/speakeasy/internal/charm/styles" 13 | ) 14 | 15 | type model struct { 16 | message string 17 | spinner spinner.Model 18 | quit bool 19 | } 20 | 21 | func initialModel(message string) *model { 22 | return &model{ 23 | message: message, 24 | spinner: *InitSpinner(), 25 | } 26 | } 27 | 28 | func InitSpinner() *spinner.Model { 29 | s := spinner.New() 30 | s.Spinner = spinner.Meter 31 | s.Style = lipgloss.NewStyle().Foreground(styles.Colors.Yellow) 32 | return &s 33 | } 34 | 35 | func (m *model) Init() tea.Cmd { 36 | return m.spinner.Tick 37 | } 38 | 39 | func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 40 | switch msg.(type) { 41 | case tea.QuitMsg: 42 | m.quit = true 43 | return m, tea.Quit 44 | } 45 | var cmd tea.Cmd 46 | m.spinner, cmd = m.spinner.Update(msg) 47 | return m, cmd 48 | } 49 | 50 | func (m *model) View() string { 51 | if m.quit { 52 | return "" 53 | } 54 | 55 | s := fmt.Sprintf("%s\n%s", styles.HeavilyEmphasized.Render(m.message), m.spinner.View()) 56 | return styles.MakeBoxed(s, styles.Colors.DimYellow, lipgloss.Center) 57 | } 58 | 59 | func StartSpinner(message string) func() { 60 | p := tea.NewProgram(initialModel(message)) 61 | go func() { 62 | _, err := p.Run() 63 | if err != nil { 64 | println(err.Error()) 65 | } 66 | }() 67 | 68 | return func() { 69 | p.Quit() 70 | // Very important, otherwise the TUI will be borked and future logs will be messed up 71 | p.ReleaseTerminal() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /internal/links/links.go: -------------------------------------------------------------------------------- 1 | package links 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/operations" 7 | "github.com/speakeasy-api/speakeasy-core/auth" 8 | "github.com/speakeasy-api/speakeasy-core/utils" 9 | "github.com/speakeasy-api/speakeasy/internal/log" 10 | ) 11 | 12 | func Shorten(ctx context.Context, url string) string { 13 | if utils.IsRunningInCI() { 14 | return url 15 | } 16 | client, err := auth.GetSDKFromContext(ctx) 17 | if err != nil { 18 | log.From(ctx).Debug(fmt.Sprintf("Failed to shorten link: %s", err.Error())) 19 | return url 20 | } 21 | 22 | res, err := client.ShortURLs.Create(ctx, operations.CreateRequestBody{URL: url}) 23 | if err != nil || res == nil || res.ShortURL == nil { 24 | log.From(ctx).Debug(fmt.Sprintf("Failed to shorten link: %s", err.Error())) 25 | return url 26 | } 27 | 28 | return res.ShortURL.ShortURL 29 | } 30 | -------------------------------------------------------------------------------- /internal/locks/locks.go: -------------------------------------------------------------------------------- 1 | package locks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | 10 | "github.com/gofrs/flock" 11 | "github.com/speakeasy-api/speakeasy/internal/singleton" 12 | ) 13 | 14 | // Package locks provides utilities for inter-process synchronization. 15 | 16 | // InterProcessMutex provides file-based mutual exclusion between processes. 17 | // It uses file locking to ensure that only one process can perform CLI updates at a time. 18 | // The lock is automatically released if the holding process dies. 19 | // 20 | // This implementation is cross-platform compatible: 21 | // - Linux/macOS: Uses flock syscall (https://linux.die.net/man/2/flock) 22 | // - Windows: Uses LockFileEx API (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) 23 | // 24 | // The mutex can be used to coordinate CLI update operations between multiple 25 | // concurrent processes to prevent race conditions during installation or updates. 26 | type InterProcessMutex struct { 27 | Opts 28 | mu *flock.Flock 29 | } 30 | 31 | type Opts struct { 32 | Name string 33 | } 34 | 35 | func new(o Opts) *InterProcessMutex { 36 | mu := flock.New(filepath.Join(os.TempDir(), o.Name)) 37 | 38 | return &InterProcessMutex{Opts: o, mu: mu} 39 | } 40 | 41 | // CLIUpdateLock creates a new inter-process mutex with default options. 42 | var CLIUpdateLock = singleton.New(func() *InterProcessMutex { 43 | return new(Opts{Name: "speakeasy-cli-update-mutex.lock"}) 44 | }) 45 | 46 | type TryLockResult struct { 47 | Attempt int 48 | Error error 49 | Success bool 50 | } 51 | 52 | func (m *InterProcessMutex) TryLock(ctx context.Context, retryDelay time.Duration) <-chan TryLockResult { 53 | ch := make(chan TryLockResult) 54 | go func() { 55 | for attempt := 0; ; attempt++ { 56 | ok, err := m.mu.TryLock() 57 | if err != nil { 58 | ch <- TryLockResult{Attempt: attempt, Error: fmt.Errorf("failed to acquire lock (pid %d): %w", os.Getpid(), err)} 59 | return 60 | } 61 | if ok { 62 | ch <- TryLockResult{Attempt: attempt, Success: true} 63 | return 64 | } 65 | 66 | select { 67 | case <-ctx.Done(): 68 | ch <- TryLockResult{Attempt: attempt, Error: ctx.Err()} 69 | return 70 | case <-time.After(retryDelay): 71 | ch <- TryLockResult{Attempt: attempt, Success: false} 72 | } 73 | } 74 | }() 75 | return ch 76 | } 77 | 78 | func (m *InterProcessMutex) Unlock() error { 79 | return m.mu.Unlock() 80 | } 81 | -------------------------------------------------------------------------------- /internal/log/logListener.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Msg struct { 4 | Msg string 5 | Type MsgType 6 | } 7 | 8 | type MsgType string 9 | 10 | var ( 11 | MsgInfo MsgType = "info" 12 | MsgWarn MsgType = "warn" 13 | MsgError MsgType = "error" 14 | MsgGithub MsgType = "github" 15 | ) 16 | -------------------------------------------------------------------------------- /internal/log/utils.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "reflect" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func PrintArray[K any](cmd *cobra.Command, arr []K, fieldNameReplacements map[string]string) { 12 | printJson, _ := cmd.Flags().GetBool("json") 13 | 14 | if printJson { 15 | data, _ := json.Marshal(arr) 16 | From(cmd.Context()).Print(string(data)) 17 | } else { 18 | PrettyPrintArray(cmd.Context(), arr, fieldNameReplacements) 19 | } 20 | } 21 | 22 | func PrintValue(cmd *cobra.Command, value interface{}, fieldNameReplacements map[string]string) { 23 | printJson, _ := cmd.Flags().GetBool("json") 24 | l := From(cmd.Context()) 25 | 26 | if printJson { 27 | data, _ := json.Marshal(value) 28 | l.Print(string(data)) 29 | } else { 30 | l.Print("--------------------------------------") 31 | PrettyPrint(cmd.Context(), value, fieldNameReplacements) 32 | } 33 | } 34 | 35 | func PrettyPrintArray[K any](ctx context.Context, arr []K, fieldNameReplacements map[string]string) { 36 | l := From(ctx) 37 | 38 | if len(arr) == 0 { 39 | l.Print("NO RESULTS") 40 | return 41 | } 42 | 43 | l.Print("--------------------------------------") 44 | for _, item := range arr { 45 | PrettyPrint(ctx, item, fieldNameReplacements) 46 | l.Print("--------------------------------------") 47 | } 48 | } 49 | 50 | func PrettyPrint(ctx context.Context, value interface{}, fieldNameReplacements map[string]string) { 51 | l := From(ctx) 52 | 53 | refVal := reflect.ValueOf(value) 54 | 55 | if refVal.Kind() == reflect.Ptr { 56 | refVal = refVal.Elem() 57 | } 58 | 59 | if refVal.Kind() != reflect.Struct { 60 | l.PrintlnUnstyled(value) 61 | } 62 | 63 | for i := 0; i < refVal.NumField(); i++ { 64 | field := refVal.Type().Field(i) 65 | fieldName := field.Name 66 | val := refVal.Field(i) 67 | 68 | if field.Type.Kind() == reflect.Ptr && !val.IsNil() { 69 | val = val.Elem() 70 | } 71 | 72 | value := val.Interface() 73 | 74 | if val.Type().Kind() == reflect.Struct || val.Type().Kind() == reflect.Map || val.Type().Kind() == reflect.Slice || val.Type().Kind() == reflect.Array { 75 | data, _ := json.Marshal(value) 76 | value = string(data) 77 | } 78 | 79 | if fieldNameReplacements != nil { 80 | if replacement, ok := fieldNameReplacements[fieldName]; ok { 81 | fieldName = replacement 82 | } 83 | } 84 | 85 | l.Printf("%s: %v", fieldName, value) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /internal/markdown/markdown.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func CreateMarkdownTable(contents [][]string) string { 10 | largestCellContentLength := 0 11 | largestNumberOfCells := 0 12 | 13 | for _, row := range contents { 14 | if len(row) > largestNumberOfCells { 15 | largestNumberOfCells = len(row) 16 | } 17 | 18 | for _, value := range row { 19 | if len(value) > largestCellContentLength { 20 | largestCellContentLength = len(value) 21 | } 22 | } 23 | } 24 | 25 | if largestCellContentLength < 3 { 26 | largestCellContentLength = 3 27 | } 28 | 29 | table := "" 30 | 31 | handledHeader := false 32 | for _, row := range contents { 33 | cellsAdded := 0 34 | for _, cell := range row { 35 | table += "| " + fmt.Sprintf("%-"+strconv.Itoa(largestCellContentLength)+"s", strings.ReplaceAll(cell, "|", "\\|")) + " " 36 | cellsAdded++ 37 | } 38 | if cellsAdded < largestNumberOfCells { 39 | for i := 0; i < largestNumberOfCells-cellsAdded; i++ { 40 | table += "| " + fmt.Sprintf("%-"+strconv.Itoa(largestCellContentLength)+"s", "") + " " 41 | } 42 | } 43 | table += "|\n" 44 | 45 | if !handledHeader { 46 | for i := 0; i < largestNumberOfCells; i++ { 47 | table += "| " + strings.Repeat("-", largestCellContentLength) + " " 48 | } 49 | table += "|\n" 50 | handledHeader = true 51 | } 52 | } 53 | 54 | return strings.Trim(table, "\n") 55 | } 56 | -------------------------------------------------------------------------------- /internal/markdown/markdown_test.go: -------------------------------------------------------------------------------- 1 | package markdown_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/speakeasy-api/speakeasy/internal/markdown" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCreateMarkdownTable(t *testing.T) { 12 | type args struct { 13 | contents [][]string 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | want string 19 | }{ 20 | { 21 | name: "Simple table with matching number of columns", 22 | args: args{ 23 | contents: [][]string{ 24 | { 25 | "Name", "Age", "Gender", 26 | }, 27 | { 28 | "John", "21", "Male", 29 | }, 30 | { 31 | "Jane", "20", "Female", 32 | }, 33 | }, 34 | }, 35 | want: ` 36 | | Name | Age | Gender | 37 | | ------ | ------ | ------ | 38 | | John | 21 | Male | 39 | | Jane | 20 | Female |`, 40 | }, 41 | { 42 | name: "Table with mismatched number of columns", 43 | args: args{ 44 | contents: [][]string{ 45 | { 46 | "Name", "Age", "Gender", 47 | }, 48 | { 49 | "John", 50 | }, 51 | { 52 | "Jane", "21", 53 | }, 54 | }, 55 | }, 56 | want: ` 57 | | Name | Age | Gender | 58 | | ------ | ------ | ------ | 59 | | John | | | 60 | | Jane | 21 | |`, 61 | }, 62 | { 63 | name: "Table with cells less than 3 characters", 64 | args: args{ 65 | contents: [][]string{ 66 | { 67 | "N", "A", "G", 68 | }, 69 | { 70 | "Jo", "20", "M", 71 | }, 72 | { 73 | "Mi", "21", "F", 74 | }, 75 | }, 76 | }, 77 | want: ` 78 | | N | A | G | 79 | | --- | --- | --- | 80 | | Jo | 20 | M | 81 | | Mi | 21 | F |`, 82 | }, 83 | { 84 | name: "Simple table with pipes escaped", 85 | args: args{ 86 | contents: [][]string{ 87 | { 88 | "Name", "Age", "Gender", 89 | }, 90 | { 91 | "Jo|hn", "21", "|Male", 92 | }, 93 | { 94 | "Jane|", "20", "Female", 95 | }, 96 | }, 97 | }, 98 | want: ` 99 | | Name | Age | Gender | 100 | | ------ | ------ | ------ | 101 | | Jo\|hn | 21 | \|Male | 102 | | Jane\| | 20 | Female |`, 103 | }, 104 | } 105 | for _, tt := range tests { 106 | t.Run(tt.name, func(t *testing.T) { 107 | got := markdown.CreateMarkdownTable(tt.args.contents) 108 | assert.Equal(t, strings.TrimPrefix(tt.want, "\n"), got) 109 | }) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /internal/model/flag/booleanFlag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type BooleanFlag struct { 10 | Name, Shorthand, Description string 11 | Required, Hidden bool 12 | DefaultValue bool 13 | Deprecated bool 14 | DeprecationMessage string 15 | } 16 | 17 | func (f BooleanFlag) Init(cmd *cobra.Command) error { 18 | cmd.Flags().BoolP(f.Name, f.Shorthand, f.DefaultValue, f.Description) 19 | if err := setRequiredAndHidden(cmd, f.Name, f.Required, f.Hidden); err != nil { 20 | return err 21 | } 22 | if f.Deprecated { 23 | if err := setDeprecated(cmd, f.Name, f.DeprecationMessage); err != nil { 24 | return err 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | func (f BooleanFlag) GetName() string { 31 | return f.Name 32 | } 33 | 34 | func (f BooleanFlag) ParseValue(v string) (interface{}, error) { 35 | return strconv.ParseBool(v) 36 | } 37 | -------------------------------------------------------------------------------- /internal/model/flag/enumFlag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type EnumFlag struct { 11 | Name, Shorthand, Description string 12 | Required, Hidden bool 13 | DefaultValue string 14 | AllowedValues []string 15 | Deprecated bool 16 | DeprecationMessage string 17 | } 18 | 19 | func (f EnumFlag) Init(cmd *cobra.Command) error { 20 | if len(f.AllowedValues) == 0 { 21 | return fmt.Errorf("allowed values must not be empty") 22 | } 23 | 24 | if !f.Required { 25 | if !slices.Contains(f.AllowedValues, f.DefaultValue) { 26 | return fmt.Errorf("default value %s is not in the list of allowed values", f.DefaultValue) 27 | } 28 | 29 | if f.DefaultValue == "" { 30 | return fmt.Errorf("default value must not be empty if the flag is not required") 31 | } 32 | } 33 | 34 | cmd.Flags().StringP(f.Name, f.Shorthand, f.DefaultValue, f.Description) 35 | if err := setRequiredAndHidden(cmd, f.Name, f.Required, f.Hidden); err != nil { 36 | return err 37 | } 38 | if f.Deprecated { 39 | if err := setDeprecated(cmd, f.Name, f.DeprecationMessage); err != nil { 40 | return err 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | func (f EnumFlag) GetName() string { 47 | return f.Name 48 | } 49 | 50 | func (f EnumFlag) ParseValue(v string) (interface{}, error) { 51 | return v, nil 52 | } 53 | -------------------------------------------------------------------------------- /internal/model/flag/flag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | type Flag interface { 8 | Init(cmd *cobra.Command) error 9 | GetName() string 10 | ParseValue(v string) (interface{}, error) 11 | } 12 | 13 | func setRequiredAndHidden(cmd *cobra.Command, name string, required, hidden bool) error { 14 | if required { 15 | if err := cmd.MarkFlagRequired(name); err != nil { 16 | return err 17 | } 18 | } 19 | if hidden { 20 | if err := cmd.Flags().MarkHidden(name); err != nil { 21 | return err 22 | } 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func setDeprecated(cmd *cobra.Command, name, message string) error { 29 | return cmd.Flags().MarkDeprecated(name, message) 30 | } 31 | 32 | // Verify that the flag types implement the Flag interface 33 | var _ = []Flag{ 34 | &StringFlag{}, 35 | &BooleanFlag{}, 36 | &IntFlag{}, 37 | &MapFlag{}, 38 | &StringSliceFlag{}, 39 | } 40 | -------------------------------------------------------------------------------- /internal/model/flag/intFlag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type IntFlag struct { 10 | Name, Shorthand, Description string 11 | Required, Hidden bool 12 | DefaultValue int 13 | Deprecated bool 14 | DeprecationMessage string 15 | } 16 | 17 | func (f IntFlag) Init(cmd *cobra.Command) error { 18 | cmd.Flags().IntP(f.Name, f.Shorthand, f.DefaultValue, f.Description) 19 | if err := setRequiredAndHidden(cmd, f.Name, f.Required, f.Hidden); err != nil { 20 | return err 21 | } 22 | if f.Deprecated { 23 | if err := setDeprecated(cmd, f.Name, f.DeprecationMessage); err != nil { 24 | return err 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | func (f IntFlag) GetName() string { 31 | return f.Name 32 | } 33 | 34 | func (f IntFlag) ParseValue(v string) (interface{}, error) { 35 | return strconv.Atoi(v) 36 | } 37 | -------------------------------------------------------------------------------- /internal/model/flag/mapFlag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // MapFlag is a flag type for a map of strings 12 | // Supports JSON input, as well as a simple key-value pair format 13 | // Examples of valid input: 14 | // --flag='{"key1":"value1","key2":"value2"}' 15 | // --flag=key1:value1,key2:value2 16 | type MapFlag struct { 17 | Name, Shorthand, Description string 18 | Required, Hidden bool 19 | DefaultValue map[string]string 20 | Deprecated bool 21 | DeprecationMessage string 22 | } 23 | 24 | func (f MapFlag) Init(cmd *cobra.Command) error { 25 | cmd.Flags().StringP(f.Name, f.Shorthand, mapToString(f.DefaultValue), f.Description) 26 | if err := setRequiredAndHidden(cmd, f.Name, f.Required, f.Hidden); err != nil { 27 | return err 28 | } 29 | if f.Deprecated { 30 | if err := setDeprecated(cmd, f.Name, f.DeprecationMessage); err != nil { 31 | return err 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | func (f MapFlag) GetName() string { 38 | return f.Name 39 | } 40 | 41 | func (f MapFlag) ParseValue(v string) (interface{}, error) { 42 | if v == "" || v == "null" { 43 | return make(map[string]string), nil 44 | } 45 | if v[0] == '{' { 46 | m := make(map[string]string) 47 | err := json.Unmarshal([]byte(v), &m) 48 | return m, err 49 | } else { 50 | return parseSimpleMap(v) 51 | } 52 | } 53 | 54 | func parseSimpleMap(v string) (map[string]string, error) { 55 | parts := strings.Split(v, ",") 56 | m := make(map[string]string) 57 | for _, part := range parts { 58 | part = strings.TrimSpace(part) 59 | part = strings.Trim(part, `"`) 60 | if part == "" { 61 | continue 62 | } 63 | 64 | kv := strings.Split(part, ":") 65 | if len(kv) != 2 { 66 | return nil, fmt.Errorf("invalid map format") 67 | } 68 | m[kv[0]] = kv[1] 69 | } 70 | 71 | return m, nil 72 | } 73 | 74 | func mapToString(m map[string]string) string { 75 | s, _ := json.Marshal(m) 76 | return string(s) 77 | } 78 | -------------------------------------------------------------------------------- /internal/model/flag/stringFlag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "github.com/speakeasy-api/speakeasy/internal/charm" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type StringFlag struct { 9 | Name, Shorthand, Description string 10 | Required, Hidden bool 11 | DefaultValue string 12 | AutocompleteFileExtensions []string 13 | Deprecated bool 14 | DeprecationMessage string 15 | } 16 | 17 | func (f StringFlag) Init(cmd *cobra.Command) error { 18 | cmd.Flags().StringP(f.Name, f.Shorthand, f.DefaultValue, f.Description) 19 | if err := setRequiredAndHidden(cmd, f.Name, f.Required, f.Hidden); err != nil { 20 | return err 21 | } 22 | if f.Deprecated { 23 | if err := setDeprecated(cmd, f.Name, f.DeprecationMessage); err != nil { 24 | return err 25 | } 26 | } 27 | if len(f.AutocompleteFileExtensions) > 0 { 28 | if err := cmd.Flags().SetAnnotation(f.Name, charm.AutoCompleteAnnotation, f.AutocompleteFileExtensions); err != nil { 29 | return err 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | func (f StringFlag) GetName() string { 36 | return f.Name 37 | } 38 | 39 | func (f StringFlag) ParseValue(v string) (interface{}, error) { 40 | return v, nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/model/flag/stringSliceFlag.go: -------------------------------------------------------------------------------- 1 | package flag 2 | 3 | import ( 4 | "encoding/csv" 5 | "strings" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type StringSliceFlag struct { 11 | Name, Shorthand, Description string 12 | Required, Hidden bool 13 | DefaultValue []string 14 | Deprecated bool 15 | DeprecationMessage string 16 | } 17 | 18 | func (f StringSliceFlag) Init(cmd *cobra.Command) error { 19 | fullDescription := f.Description + " (comma-separated list)" 20 | 21 | cmd.Flags().StringSliceP(f.Name, f.Shorthand, f.DefaultValue, fullDescription) 22 | if err := setRequiredAndHidden(cmd, f.Name, f.Required, f.Hidden); err != nil { 23 | return err 24 | } 25 | if f.Deprecated { 26 | if err := setDeprecated(cmd, f.Name, f.DeprecationMessage); err != nil { 27 | return err 28 | } 29 | } 30 | return nil 31 | } 32 | 33 | func (f StringSliceFlag) GetName() string { 34 | return f.Name 35 | } 36 | 37 | func (f StringSliceFlag) ParseValue(v string) (interface{}, error) { 38 | // Remove the brackets from the string 39 | v = v[1 : len(v)-1] 40 | 41 | if v == "" { 42 | return []string{}, nil 43 | } 44 | 45 | stringReader := strings.NewReader(v) 46 | csvReader := csv.NewReader(stringReader) 47 | return csvReader.Read() 48 | } 49 | -------------------------------------------------------------------------------- /internal/run/criticalWarnings.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import "regexp" 4 | 5 | var criticalWarnings = []*regexp.Regexp{ 6 | regexp.MustCompile(`skipping.*unsupported`), 7 | regexp.MustCompile(`registry tracking failed`), 8 | } 9 | 10 | func getCriticalWarnings(warnLogs []string) []string { 11 | var critical []string 12 | 13 | for _, log := range warnLogs { 14 | for _, w := range criticalWarnings { 15 | if w.MatchString(log) { 16 | critical = append(critical, log) 17 | } 18 | } 19 | } 20 | 21 | return critical 22 | } 23 | -------------------------------------------------------------------------------- /internal/run/merge.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/speakeasy-api/sdk-gen-config/workflow" 10 | "github.com/speakeasy-api/speakeasy/internal/log" 11 | "github.com/speakeasy-api/speakeasy/internal/schemas" 12 | "github.com/speakeasy-api/speakeasy/internal/workflowTracking" 13 | "github.com/speakeasy-api/speakeasy/pkg/merge" 14 | ) 15 | 16 | type Merge struct { 17 | workflow *Workflow 18 | parentStep *workflowTracking.WorkflowStep 19 | source workflow.Source 20 | ruleset string 21 | } 22 | 23 | var _ SourceStep = Merge{} 24 | 25 | func NewMerge(w *Workflow, parentStep *workflowTracking.WorkflowStep, source workflow.Source, ruleset string) Merge { 26 | return Merge{ 27 | workflow: w, 28 | parentStep: parentStep, 29 | source: source, 30 | ruleset: ruleset, 31 | } 32 | } 33 | 34 | func (m Merge) Do(ctx context.Context, _ string) (string, error) { 35 | mergeStep := m.parentStep.NewSubstep("Merge Documents") 36 | 37 | mergeLocation := m.source.GetTempMergeLocation() 38 | 39 | log.From(ctx).Infof("Merging %d schemas into %s...", len(m.source.Inputs), mergeLocation) 40 | 41 | inSchemas := []string{} 42 | for _, input := range m.source.Inputs { 43 | resolvedPath, err := schemas.ResolveDocument(ctx, input, nil, mergeStep) 44 | if err != nil { 45 | return "", err 46 | } 47 | inSchemas = append(inSchemas, resolvedPath) 48 | } 49 | 50 | mergeStep.NewSubstep(fmt.Sprintf("Merge %d documents", len(m.source.Inputs))) 51 | 52 | if err := mergeDocuments(ctx, inSchemas, mergeLocation, m.ruleset, m.workflow.ProjectDir, m.workflow.SkipGenerateLintReport); err != nil { 53 | return "", err 54 | } 55 | 56 | return mergeLocation, nil 57 | } 58 | 59 | func mergeDocuments(ctx context.Context, inSchemas []string, outFile, defaultRuleset, workingDir string, skipGenerateLintReport bool) error { 60 | if err := os.MkdirAll(filepath.Dir(outFile), os.ModePerm); err != nil { 61 | return err 62 | } 63 | 64 | if err := merge.MergeOpenAPIDocuments(ctx, inSchemas, outFile, defaultRuleset, workingDir, skipGenerateLintReport); err != nil { 65 | return err 66 | } 67 | 68 | log.From(ctx).Printf("Successfully merged %d schemas into %s", len(inSchemas), outFile) 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /internal/run/minimumViableSpec.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/AlekSi/pointer" 9 | "github.com/speakeasy-api/openapi-generation/v2/pkg/errors" 10 | "github.com/speakeasy-api/sdk-gen-config/workflow" 11 | "github.com/speakeasy-api/speakeasy/internal/charm/styles" 12 | "github.com/speakeasy-api/speakeasy/internal/studio/modifications" 13 | "github.com/speakeasy-api/speakeasy/internal/workflowTracking" 14 | ) 15 | 16 | func (w *Workflow) retryWithMinimumViableSpec(ctx context.Context, parentStep *workflowTracking.WorkflowStep, sourceID, targetID string, vErrs []error) (string, *SourceResult, error) { 17 | targetLanguage := w.workflow.Targets[targetID].Target 18 | var invalidOperations []string 19 | for _, err := range vErrs { 20 | vErr := errors.GetValidationErr(err) 21 | if vErr.Severity == errors.SeverityError { 22 | for _, op := range vErr.AffectedOperationIDs { 23 | invalidOperations = append(invalidOperations, op) 24 | } 25 | } 26 | } 27 | 28 | substep := parentStep.NewSubstep("Retrying with minimum viable document") 29 | source := w.workflow.Sources[sourceID] 30 | 31 | if len(invalidOperations) > 0 { 32 | source.Transformations = append(source.Transformations, workflow.Transformation{ 33 | FilterOperations: &workflow.FilterOperationsOptions{ 34 | Operations: strings.Join(invalidOperations, ","), 35 | Exclude: pointer.ToBool(true), 36 | }, 37 | }) 38 | } else { 39 | // Sometimes the document has invalid, unused sections 40 | source.Transformations = append(source.Transformations, workflow.Transformation{ 41 | RemoveUnused: pointer.ToBool(true), 42 | }) 43 | } 44 | w.workflow.Sources[sourceID] = source 45 | 46 | sourcePath, sourceRes, err := w.RunSource(ctx, substep, sourceID, targetID, targetLanguage) 47 | if err != nil { 48 | return "", nil, fmt.Errorf("failed to re-run source: %w", err) 49 | } 50 | 51 | if err := workflow.Save(w.ProjectDir, &w.workflow); err != nil { 52 | return "", nil, fmt.Errorf("failed to save workflow: %w", err) 53 | } 54 | 55 | return sourcePath, sourceRes, err 56 | } 57 | 58 | func groupInvalidOperations(input []string) []string { 59 | var result []string 60 | for _, op := range input[0:7] { 61 | joined := styles.DimmedItalic.Render(fmt.Sprintf("- %s", op)) 62 | result = append(result, joined) 63 | } 64 | 65 | if len(input) > 7 { 66 | result = append(result, styles.DimmedItalic.Render(fmt.Sprintf("- ... see %s", modifications.OverlayPath))) 67 | } 68 | 69 | return result 70 | } 71 | -------------------------------------------------------------------------------- /internal/run/remote_test.go: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseGitHubRepoURL(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | url string 11 | wantOrg string 12 | wantRepo string 13 | wantErr bool 14 | }{ 15 | { 16 | name: "HTTPS URL", 17 | url: "https://github.com/speakeasy-api/speakeasy-cli", 18 | wantOrg: "speakeasy-api", 19 | wantRepo: "speakeasy-cli", 20 | wantErr: false, 21 | }, 22 | { 23 | name: "HTTPS URL with trailing slash", 24 | url: "https://github.com/speakeasy-api/speakeasy-cli/", 25 | wantOrg: "speakeasy-api", 26 | wantRepo: "speakeasy-cli", 27 | wantErr: false, 28 | }, 29 | { 30 | name: "SSH URL", 31 | url: "git@github.com:speakeasy-api/speakeasy-cli.git", 32 | wantOrg: "speakeasy-api", 33 | wantRepo: "speakeasy-cli", 34 | wantErr: false, 35 | }, 36 | { 37 | name: "Simple org/repo format", 38 | url: "speakeasy-api/speakeasy-cli", 39 | wantOrg: "speakeasy-api", 40 | wantRepo: "speakeasy-cli", 41 | wantErr: false, 42 | }, 43 | { 44 | name: "Invalid URL format", 45 | url: "github.com/speakeasy-api/speakeasy-cli", 46 | wantErr: true, 47 | }, 48 | { 49 | name: "Invalid org/repo format", 50 | url: "speakeasy-api-speakeasy-cli", 51 | wantErr: true, 52 | }, 53 | } 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | gotOrg, gotRepo, err := parseGitHubRepoURL(tt.url) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("parseGitHubRepoURL() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | if gotOrg != tt.wantOrg { 63 | t.Errorf("parseGitHubRepoURL() gotOrg = %v, want %v", gotOrg, tt.wantOrg) 64 | } 65 | if gotRepo != tt.wantRepo { 66 | t.Errorf("parseGitHubRepoURL() gotRepo = %v, want %v", gotRepo, tt.wantRepo) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/schemas/document.go: -------------------------------------------------------------------------------- 1 | package schemas 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/pb33f/libopenapi" 7 | v3 "github.com/pb33f/libopenapi/datamodel/high/v3" 8 | "github.com/speakeasy-api/sdk-gen-config/workflow" 9 | "github.com/speakeasy-api/speakeasy-core/openapi" 10 | "github.com/speakeasy-api/speakeasy/internal/download" 11 | "github.com/speakeasy-api/speakeasy/internal/utils" 12 | "github.com/speakeasy-api/speakeasy/internal/workflowTracking" 13 | "github.com/speakeasy-api/speakeasy/registry" 14 | ) 15 | 16 | func LoadDocument(ctx context.Context, schemaLocation string) ([]byte, *libopenapi.Document, *libopenapi.DocumentModel[v3.Document], error) { 17 | docPath, err := ResolveDocument(ctx, workflow.Document{Location: workflow.LocationString(schemaLocation)}, nil, nil) 18 | if err != nil { 19 | return nil, nil, nil, err 20 | } 21 | 22 | return openapi.LoadDocument(ctx, docPath) 23 | } 24 | 25 | func ResolveDocument(ctx context.Context, d workflow.Document, outputLocation *string, step *workflowTracking.WorkflowStep) (string, error) { 26 | if d.IsSpeakeasyRegistry() { 27 | step.NewSubstep("Downloading registry bundle") 28 | if !registry.IsRegistryEnabled(ctx) { 29 | return "", fmt.Errorf("schema registry is not enabled for this workspace") 30 | } 31 | 32 | location := workflow.GetTempDir() 33 | documentOut, err := registry.ResolveSpeakeasyRegistryBundle(ctx, d, location) 34 | if err != nil { 35 | return "", err 36 | } 37 | 38 | // Note that workflows with inputs from the registry will not work with $refs to other files in the bundle 39 | if outputLocation != nil { 40 | // Copy actual document out of bundle over to outputLocation 41 | if err := utils.CopyFile(documentOut.LocalFilePath, *outputLocation); err != nil { 42 | return "", err 43 | } 44 | } 45 | 46 | return documentOut.LocalFilePath, nil 47 | } else if d.IsRemote() { 48 | step.NewSubstep("Downloading remote document") 49 | location := d.GetTempDownloadPath(workflow.GetTempDir()) 50 | if outputLocation != nil { 51 | location = *outputLocation 52 | } 53 | 54 | documentOut, err := download.ResolveRemoteDocument(ctx, d, location) 55 | if err != nil { 56 | return "", err 57 | } 58 | 59 | return documentOut, nil 60 | } 61 | 62 | return d.Location.Resolve(), nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/schemas/format.go: -------------------------------------------------------------------------------- 1 | package schemas 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "github.com/pb33f/libopenapi/json" 8 | "github.com/speakeasy-api/speakeasy-core/openapi" 9 | "github.com/speakeasy-api/speakeasy/internal/utils" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | // Format reformats a document to the desired output format while preserving key ordering 14 | // Can be used to convert output types, or improve readability (e.g. prettifying single-line documents) 15 | func Format(ctx context.Context, schemaPath string, yamlOut bool) ([]byte, error) { 16 | _, _, model, err := LoadDocument(ctx, schemaPath) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to parse document: %w", err) 19 | } 20 | 21 | return Render(model.Index.GetRootNode(), schemaPath, yamlOut) 22 | } 23 | 24 | func Render(y *yaml.Node, schemaPath string, yamlOut bool) ([]byte, error) { 25 | yamlIn := utils.HasYAMLExt(schemaPath) 26 | return RenderDocument(y, schemaPath, yamlIn, yamlOut) 27 | } 28 | 29 | // RenderDocument - schemaPath can be unset if the docuemnt does not need reference resolution 30 | func RenderDocument(y *yaml.Node, schemaPath string, yamlIn bool, yamlOut bool) ([]byte, error) { 31 | if yamlIn && yamlOut { 32 | var res bytes.Buffer 33 | encoder := yaml.NewEncoder(&res) 34 | // Note: would love to make this generic but the indentation information isn't in go-yaml nodes 35 | // https://github.com/go-yaml/yaml/issues/899 36 | encoder.SetIndent(2) 37 | if err := encoder.Encode(y); err != nil { 38 | return nil, fmt.Errorf("failed to encode YAML: %w", err) 39 | } 40 | return res.Bytes(), nil 41 | } 42 | 43 | // Preserves key ordering 44 | specBytes, err := json.YAMLNodeToJSON(y, " ") 45 | if err != nil { 46 | return nil, fmt.Errorf("failed to convert YAML to JSON: %w", err) 47 | } 48 | 49 | if yamlOut { 50 | // Use libopenapi to convert JSON to YAML to preserve key ordering 51 | _, model, err := openapi.Load(specBytes, schemaPath) 52 | if err != nil { 53 | return nil, fmt.Errorf("failed to load document: %w", err) 54 | } 55 | 56 | yamlBytes := model.Model.RenderWithIndention(2) 57 | 58 | return yamlBytes, nil 59 | } else { 60 | return specBytes, nil 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /internal/sdk/sdk.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | speakeasy "github.com/speakeasy-api/speakeasy-client-sdk-go/v3" 8 | "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" 9 | 10 | "github.com/speakeasy-api/speakeasy/internal/config" 11 | ) 12 | 13 | func InitSDK(opts ...speakeasy.SDKOption) (*speakeasy.Speakeasy, error) { 14 | return InitSDKWithKey(config.GetSpeakeasyAPIKey(), opts...) 15 | } 16 | 17 | func InitSDKWithKey(apiKey string, opts ...speakeasy.SDKOption) (*speakeasy.Speakeasy, error) { 18 | if apiKey == "" { 19 | return nil, errors.New("no api key available, please set SPEAKEASY_API_KEY or run 'speakeasy auth' to authenticate the CLI with the Speakeasy Platform") 20 | } 21 | 22 | opts = append(opts, speakeasy.WithSecurity(shared.Security{APIKey: &apiKey})) 23 | 24 | serverURL := os.Getenv("SPEAKEASY_SERVER_URL") 25 | if serverURL != "" { 26 | opts = append(opts, speakeasy.WithServerURL(serverURL)) 27 | } 28 | 29 | s := speakeasy.New(opts...) 30 | 31 | return s, nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/singleton/singleton.go: -------------------------------------------------------------------------------- 1 | // Package singleton provides a generic implementation of the singleton pattern. 2 | package singleton 3 | 4 | import "sync" 5 | 6 | type singleton[T any, R any] struct { 7 | once sync.Once 8 | instance R 9 | } 10 | 11 | // New creates a singleton factory function. 12 | // Returns a function that will always return the same instance. 13 | func New[R any](constructor func() R) func() R { 14 | var s singleton[struct{}, R] 15 | return func() R { 16 | s.once.Do(func() { 17 | s.instance = constructor() 18 | }) 19 | return s.instance 20 | } 21 | } 22 | 23 | // NewWithOpts creates a singleton factory function that accepts a config parameter. 24 | // Returns a function that will always return the same instance. 25 | func NewWithOpts[T any, R any](constructor func(T) R) func(T) R { 26 | var s singleton[T, R] 27 | return func(cfg T) R { 28 | s.once.Do(func() { 29 | s.instance = constructor(cfg) 30 | }) 31 | return s.instance 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/studio/sdk/.genignore: -------------------------------------------------------------------------------- 1 | go.mod 2 | go.sum 3 | .gitattributes 4 | -------------------------------------------------------------------------------- /internal/studio/sdk/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated=true 2 | -------------------------------------------------------------------------------- /internal/studio/sdk/.gitignore: -------------------------------------------------------------------------------- 1 | .speakeasy/reports 2 | # .gitignore 3 | -------------------------------------------------------------------------------- /internal/studio/sdk/.speakeasy/gen.yaml: -------------------------------------------------------------------------------- 1 | configVersion: 2.0.0 2 | generation: 3 | sdkClassName: SDK 4 | maintainOpenAPIOrder: true 5 | usageSnippets: 6 | optionalPropertyRendering: withExample 7 | useClassNamesForArrayFields: true 8 | fixes: 9 | nameResolutionDec2023: true 10 | nameResolutionFeb2025: false 11 | parameterOrderingFeb2024: true 12 | requestResponseComponentNamesFeb2024: true 13 | securityFeb2025: false 14 | auth: 15 | oAuth2ClientCredentialsEnabled: false 16 | oAuth2PasswordEnabled: false 17 | go: 18 | version: 0.10.7 19 | additionalDependencies: {} 20 | allowUnknownFieldsInWeakUnions: false 21 | clientServerStatusCodesAsErrors: true 22 | defaultErrorName: SDKError 23 | flattenGlobalSecurity: true 24 | imports: 25 | option: openapi 26 | paths: 27 | callbacks: models/callbacks 28 | errors: models/sdkerrors 29 | operations: models/operations 30 | shared: models/components 31 | webhooks: models/webhooks 32 | inputModelSuffix: input 33 | maxMethodParams: 4 34 | methodArguments: require-security-and-request 35 | outputModelSuffix: output 36 | packageName: github.com/speakeasy-api/speakeasy/internal/studio/sdk 37 | responseFormat: envelope-http 38 | -------------------------------------------------------------------------------- /internal/studio/sdk/.speakeasy/speakeasy-modifications-overlay.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: speakeasy-studio-modifications 4 | version: 1.0.0 5 | actions: 6 | - target: $["paths"]["/run"]["get"] 7 | x-speakeasy-modification: 8 | created_at: 1723739390834 9 | reviewed_at: 1725122245820 10 | type: method-name 11 | before: sdk..getRun() 12 | after: sdk.run.getLastResult() 13 | update: 14 | x-speakeasy-group: run 15 | x-speakeasy-name-override: getLastResult 16 | - target: $["paths"]["/run"]["post"] 17 | x-speakeasy-modification: 18 | created_at: 1725122168100 19 | reviewed_at: 1725122245820 20 | type: method-name 21 | before: sdk..run() 22 | after: sdk.run.reRun() 23 | update: 24 | x-speakeasy-group: run 25 | x-speakeasy-name-override: reRun 26 | - target: $["paths"]["/health"]["get"] 27 | x-speakeasy-modification: 28 | created_at: 1725122168100 29 | reviewed_at: 1725122245820 30 | type: method-name 31 | before: sdk..checkHealth() 32 | after: sdk.health.check() 33 | update: 34 | x-speakeasy-group: health 35 | x-speakeasy-name-override: check 36 | - target: $["paths"]["/suggest/method-names"]["get"] 37 | x-speakeasy-modification: 38 | created_at: 1725122168100 39 | reviewed_at: 1725122245820 40 | type: method-name 41 | before: sdk..suggestMethodNames() 42 | after: sdk.suggest.methodNames() 43 | update: 44 | x-speakeasy-group: suggest 45 | x-speakeasy-name-override: methodNames 46 | -------------------------------------------------------------------------------- /internal/studio/sdk/.speakeasy/workflow.lock: -------------------------------------------------------------------------------- 1 | speakeasyVersion: 1.468.2 2 | sources: 3 | SpeakeasyStudio-OAS: 4 | sourceNamespace: speakeasy-studio-oas 5 | sourceRevisionDigest: sha256:9018148e02c465b1ce58f688d7cb6790d982c9d684495be78d696ea1bc1c6f7f 6 | sourceBlobDigest: sha256:e9f18d4d27ef0f70700960cc516419ffe4e11c7add6c727d188ea3404a0ee162 7 | tags: 8 | - latest 9 | - 1.0.0 10 | SpekaeasyStudio-OAS: 11 | sourceNamespace: spekaeasy-studio-oas 12 | sourceRevisionDigest: sha256:f5da666ea15ed240ee9a4db4fdb3657ac19de906e474adeb0a84af9ffa6b9aa9 13 | sourceBlobDigest: sha256:78026283d1ab636c2946eaaa88c61a11a764e3fb98b16078864ca8d1a96cfcf6 14 | tags: 15 | - latest 16 | targets: 17 | speakeasy-studio: 18 | source: SpeakeasyStudio-OAS 19 | sourceNamespace: speakeasy-studio-oas 20 | sourceRevisionDigest: sha256:9018148e02c465b1ce58f688d7cb6790d982c9d684495be78d696ea1bc1c6f7f 21 | sourceBlobDigest: sha256:e9f18d4d27ef0f70700960cc516419ffe4e11c7add6c727d188ea3404a0ee162 22 | spekaeasy-studio: 23 | source: SpekaeasyStudio-OAS 24 | sourceNamespace: spekaeasy-studio-oas 25 | sourceRevisionDigest: sha256:f5da666ea15ed240ee9a4db4fdb3657ac19de906e474adeb0a84af9ffa6b9aa9 26 | sourceBlobDigest: sha256:78026283d1ab636c2946eaaa88c61a11a764e3fb98b16078864ca8d1a96cfcf6 27 | workflow: 28 | workflowVersion: 1.0.0 29 | sources: 30 | SpeakeasyStudio-OAS: 31 | inputs: 32 | - location: ../oas_studio.yaml 33 | overlays: 34 | - location: .speakeasy/speakeasy-modifications-overlay.yaml 35 | registry: 36 | location: registry.speakeasyapi.dev/speakeasy-self/speakeasy-self/speakeasy-studio-oas 37 | targets: 38 | speakeasy-studio: 39 | target: go 40 | source: SpeakeasyStudio-OAS 41 | -------------------------------------------------------------------------------- /internal/studio/sdk/.speakeasy/workflow.yaml: -------------------------------------------------------------------------------- 1 | workflowVersion: 1.0.0 2 | speakeasyVersion: latest 3 | sources: 4 | SpeakeasyStudio-OAS: 5 | inputs: 6 | - location: ../oas_studio.yaml 7 | overlays: 8 | - location: .speakeasy/speakeasy-modifications-overlay.yaml 9 | registry: 10 | location: registry.speakeasyapi.dev/speakeasy-self/speakeasy-self/speakeasy-studio-oas 11 | targets: 12 | speakeasy-studio: 13 | target: go 14 | source: SpeakeasyStudio-OAS 15 | -------------------------------------------------------------------------------- /internal/studio/sdk/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to This Repository 2 | 3 | Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements. 4 | 5 | ## How to Report Issues 6 | 7 | If you encounter any bugs or have suggestions for improvements, please open an issue on GitHub. When reporting an issue, please provide as much detail as possible to help us reproduce the problem. This includes: 8 | 9 | - A clear and descriptive title 10 | - Steps to reproduce the issue 11 | - Expected and actual behavior 12 | - Any relevant logs, screenshots, or error messages 13 | - Information about your environment (e.g., operating system, software versions) 14 | - For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed 15 | 16 | ## Issue Triage and Upstream Fixes 17 | 18 | We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code. 19 | 20 | ## Contact 21 | 22 | If you have any questions or need further assistance, please feel free to reach out by opening an issue. 23 | 24 | Thank you for your understanding and cooperation! 25 | 26 | The Maintainers 27 | -------------------------------------------------------------------------------- /internal/studio/sdk/USAGE.md: -------------------------------------------------------------------------------- 1 | 2 | ```go 3 | package main 4 | 5 | import ( 6 | "context" 7 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk" 8 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 9 | "log" 10 | ) 11 | 12 | func main() { 13 | ctx := context.Background() 14 | 15 | s := sdk.New( 16 | sdk.WithSecurity(""), 17 | ) 18 | 19 | res, err := s.GenerateOverlay(ctx, components.OverlayCompareRequestBody{ 20 | Before: "", 21 | After: "", 22 | }) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | if res.OverlayCompareResponse != nil { 27 | // handle response 28 | } 29 | } 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/auth.md: -------------------------------------------------------------------------------- 1 | # Auth 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------ | ------------------ | ------------------ | ------------------ | 8 | | `AuthHeader` | *string* | :heavy_check_mark: | Auth header | 9 | | `AuthSecret` | *string* | :heavy_check_mark: | Auth secret | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/codesamples.md: -------------------------------------------------------------------------------- 1 | # CodeSamples 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | 8 | | `Output` | **string* | :heavy_minus_sign: | Output string | 9 | | `Registry` | [*components.SourceRegistry](../../models/components/sourceregistry.md) | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/data.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------ | ------------------ | ------------------ | ------------------ | 8 | | `Status` | **string* | :heavy_minus_sign: | Status of the CLI | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/diagnostic.md: -------------------------------------------------------------------------------- 1 | # Diagnostic 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | 8 | | `Message` | *string* | :heavy_check_mark: | Message describing the issue | 9 | | `Severity` | *string* | :heavy_check_mark: | Severity | 10 | | `Line` | **int64* | :heavy_minus_sign: | Line number | 11 | | `Path` | []*string* | :heavy_minus_sign: | Schema path to the issue | 12 | | `Type` | *string* | :heavy_check_mark: | Issue type | 13 | | `HelpMessage` | **string* | :heavy_minus_sign: | Help message for how to fix the issue | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/document.md: -------------------------------------------------------------------------------- 1 | # Document 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | 8 | | `Location` | *string* | :heavy_check_mark: | Document location | 9 | | `Auth` | [*components.Auth](../../models/components/auth.md) | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/fallbackcodesamples.md: -------------------------------------------------------------------------------- 1 | # FallbackCodeSamples 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ----------- | ----------- | ----------- | ----------- | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/healthresponse.md: -------------------------------------------------------------------------------- 1 | # HealthResponse 2 | 3 | Successful response 4 | 5 | 6 | ## Fields 7 | 8 | | Field | Type | Required | Description | 9 | | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | 10 | | `Data` | [*components.Data](../../models/components/data.md) | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/httpmetadata.md: -------------------------------------------------------------------------------- 1 | # HTTPMetadata 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | 8 | | `Response` | [*http.Response](https://pkg.go.dev/net/http#Response) | :heavy_check_mark: | Raw HTTP response; suitable for custom response parsing | 9 | | `Request` | [*http.Request](https://pkg.go.dev/net/http#Request) | :heavy_check_mark: | Raw HTTP request; suitable for debugging | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/overlay.md: -------------------------------------------------------------------------------- 1 | # Overlay 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | 8 | | `FallbackCodeSamples` | [*components.FallbackCodeSamples](../../models/components/fallbackcodesamples.md) | :heavy_minus_sign: | N/A | 9 | | `Document` | [*components.OverlayDocument](../../models/components/overlaydocument.md) | :heavy_minus_sign: | Document information | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/overlaycomparerequestbody.md: -------------------------------------------------------------------------------- 1 | # OverlayCompareRequestBody 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | -------------------- | -------------------- | -------------------- | -------------------- | 8 | | `Before` | *string* | :heavy_check_mark: | The first yaml file | 9 | | `After` | *string* | :heavy_check_mark: | The second yaml file | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/overlaycompareresponse.md: -------------------------------------------------------------------------------- 1 | # OverlayCompareResponse 2 | 3 | Successful response 4 | 5 | 6 | ## Fields 7 | 8 | | Field | Type | Required | Description | 9 | | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | 10 | | `Overlay` | *string* | :heavy_check_mark: | The studio modifications overlay contents - this should be an overlay YAML document | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/overlaydocument.md: -------------------------------------------------------------------------------- 1 | # OverlayDocument 2 | 3 | Document information 4 | 5 | 6 | ## Fields 7 | 8 | | Field | Type | Required | Description | 9 | | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | 10 | | `Location` | *string* | :heavy_check_mark: | Document location | 11 | | `Auth` | [*components.Auth](../../models/components/auth.md) | :heavy_minus_sign: | N/A | 12 | | `Contents` | **string* | :heavy_minus_sign: | Document contents | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ----------- | ----------- | ----------- | ----------- | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/runrequestbody.md: -------------------------------------------------------------------------------- 1 | # RunRequestBody 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | 8 | | `Overlay` | *string* | :heavy_check_mark: | The studio modifications overlay contents - this should be an overlay YAML document | 9 | | `Input` | *string* | :heavy_check_mark: | The input spec for the source | 10 | | `Targets` | map[string][components.TargetSpecificInputs](../../models/components/targetspecificinputs.md) | :heavy_check_mark: | Map of target specific inputs keyed on target name
Only present if a target input is modified
| -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/runresponse.md: -------------------------------------------------------------------------------- 1 | # RunResponse 2 | 3 | Successful response 4 | 5 | 6 | ## Fields 7 | 8 | | Field | Type | Required | Description | 9 | | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | 10 | | `Event` | *string* | :heavy_check_mark: | Type of the stream | 11 | | `Data` | [components.RunResponseData](../../models/components/runresponsedata.md) | :heavy_check_mark: | Map of target run summaries | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------ | ------------------ | ------------------ | ------------------ | 8 | | `Secret` | *string* | :heavy_check_mark: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/source.md: -------------------------------------------------------------------------------- 1 | # Source 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | 8 | | `Inputs` | [][components.Document](../../models/components/document.md) | :heavy_minus_sign: | List of input documents | 9 | | `Overlays` | [][components.Overlay](../../models/components/overlay.md) | :heavy_minus_sign: | List of overlays | 10 | | `Output` | **string* | :heavy_minus_sign: | Output string | 11 | | `Ruleset` | **string* | :heavy_minus_sign: | Ruleset string | 12 | | `Registry` | [*components.SourceRegistry](../../models/components/sourceregistry.md) | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/sourceregistry.md: -------------------------------------------------------------------------------- 1 | # SourceRegistry 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------ | ------------------------ | ------------------------ | ------------------------ | 8 | | `Location` | **string* | :heavy_minus_sign: | Source registry location | 9 | | `Tags` | []*string* | :heavy_minus_sign: | List of tags | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/sourceresponsedata.md: -------------------------------------------------------------------------------- 1 | # SourceResponseData 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | 8 | | `SourceID` | *string* | :heavy_check_mark: | Source ID in the workflow file | 9 | | `Input` | *string* | :heavy_check_mark: | The merged input specs for the source | 10 | | `Overlay` | *string* | :heavy_check_mark: | Studio modifications overlay contents (could be empty string) | 11 | | `OverlayPath` | *string* | :heavy_check_mark: | Studio modifications overlay path | 12 | | `Output` | *string* | :heavy_check_mark: | Result of running the source in the workflow | 13 | | `Diagnosis` | [][components.Diagnostic](../../models/components/diagnostic.md) | :heavy_minus_sign: | List of validation errors | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/suggestresponse.md: -------------------------------------------------------------------------------- 1 | # SuggestResponse 2 | 3 | Successful response 4 | 5 | 6 | ## Fields 7 | 8 | | Field | Type | Required | Description | 9 | | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | 10 | | `Overlay` | *string* | :heavy_check_mark: | The studio modifications overlay contents - this should be an overlay YAML document | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/target.md: -------------------------------------------------------------------------------- 1 | # Target 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ----------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | 8 | | `Target` | *string* | :heavy_check_mark: | Target language | 9 | | `Source` | *string* | :heavy_check_mark: | Source ID | 10 | | `Output` | **string* | :heavy_minus_sign: | Output string | 11 | | `Publishing` | [*components.Publishing](../../models/components/publishing.md) | :heavy_minus_sign: | N/A | 12 | | `CodeSamples` | [*components.CodeSamples](../../models/components/codesamples.md) | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/targetrunsummary.md: -------------------------------------------------------------------------------- 1 | # TargetRunSummary 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- | 8 | | `Readme` | *string* | :heavy_check_mark: | Contents of the README file for this target | 9 | | `GenYaml` | *string* | :heavy_check_mark: | Contents of the gen.yaml file for this target | 10 | | `GenYamlPath` | **string* | :heavy_minus_sign: | The path to the gen.yaml file for this target | 11 | | `OutputDirectory` | *string* | :heavy_check_mark: | Output directory for this target | 12 | | `Language` | *string* | :heavy_check_mark: | Language for this target | 13 | | `SourceID` | *string* | :heavy_check_mark: | Source ID in the workflow file | 14 | | `TargetID` | *string* | :heavy_check_mark: | Target ID in the workflow file | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/targetspecificinputs.md: -------------------------------------------------------------------------------- 1 | # TargetSpecificInputs 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | 8 | | `Config` | *string* | :heavy_check_mark: | New contents of the gen.yaml file for this target | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/components/workflow.md: -------------------------------------------------------------------------------- 1 | # Workflow 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ----------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | 8 | | `Version` | *string* | :heavy_check_mark: | Workflow version | 9 | | `SpeakeasyVersion` | *string* | :heavy_check_mark: | Speakeasy version | 10 | | `Sources` | map[string][components.Source](../../models/components/source.md) | :heavy_check_mark: | Map of sources | 11 | | `Targets` | map[string][components.Target](../../models/components/target.md) | :heavy_check_mark: | Map of targets | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/checkhealthresponse.md: -------------------------------------------------------------------------------- 1 | # CheckHealthResponse 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | 8 | | `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | 9 | | `HealthResponse` | **stream.EventStream[components.HealthResponse]* | :heavy_minus_sign: | Successful response | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/exitresponse.md: -------------------------------------------------------------------------------- 1 | # ExitResponse 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | 8 | | `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/generateoverlayresponse.md: -------------------------------------------------------------------------------- 1 | # GenerateOverlayResponse 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | 8 | | `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | 9 | | `OverlayCompareResponse` | [*components.OverlayCompareResponse](../../models/components/overlaycompareresponse.md) | :heavy_minus_sign: | Successful response | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/getrunresponse.md: -------------------------------------------------------------------------------- 1 | # GetRunResponse 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | 8 | | `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | 9 | | `RunResponse` | **stream.EventStream[components.RunResponse]* | :heavy_minus_sign: | Successful response | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/option.md: -------------------------------------------------------------------------------- 1 | ## Options 2 | 3 | ### WithServerURL 4 | 5 | WithServerURL allows providing an alternative server URL. 6 | 7 | ```go 8 | operations.WithServerURL("http://api.example.com") 9 | ``` 10 | 11 | ## WithTemplatedServerURL 12 | 13 | WithTemplatedServerURL allows providing an alternative server URL with templated parameters. 14 | 15 | ```go 16 | operations.WithTemplatedServerURL("http://{host}:{port}", map[string]string{ 17 | "host": "api.example.com", 18 | "port": "8080", 19 | }) 20 | ``` 21 | 22 | ### WithRetries 23 | 24 | WithRetries allows customizing the default retry configuration. Only usable with methods that mention they support retries. 25 | 26 | ```go 27 | operations.WithRetries(retry.Config{ 28 | Strategy: "backoff", 29 | Backoff: retry.BackoffStrategy{ 30 | InitialInterval: 500 * time.Millisecond, 31 | MaxInterval: 60 * time.Second, 32 | Exponent: 1.5, 33 | MaxElapsedTime: 5 * time.Minute, 34 | }, 35 | RetryConnectionErrors: true, 36 | }) 37 | ``` -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/runresponse.md: -------------------------------------------------------------------------------- 1 | # RunResponse 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | 8 | | `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | 9 | | `RunResponse` | **stream.EventStream[components.RunResponse]* | :heavy_minus_sign: | Successful response | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/models/operations/suggestmethodnamesresponse.md: -------------------------------------------------------------------------------- 1 | # SuggestMethodNamesResponse 2 | 3 | 4 | ## Fields 5 | 6 | | Field | Type | Required | Description | 7 | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | 8 | | `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | 9 | | `SuggestResponse` | [*components.SuggestResponse](../../models/components/suggestresponse.md) | :heavy_minus_sign: | Successful response | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/sdks/health/README.md: -------------------------------------------------------------------------------- 1 | # Health 2 | (*Health*) 3 | 4 | ## Overview 5 | 6 | ### Available Operations 7 | 8 | * [Check](#check) - Health Check 9 | 10 | ## Check 11 | 12 | Check the CLI health and return relevant information. 13 | 14 | ### Example Usage 15 | 16 | ```go 17 | package main 18 | 19 | import( 20 | "context" 21 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk" 22 | "log" 23 | ) 24 | 25 | func main() { 26 | ctx := context.Background() 27 | 28 | s := sdk.New( 29 | sdk.WithSecurity(""), 30 | ) 31 | 32 | res, err := s.Health.Check(ctx) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | if res.HealthResponse != nil { 37 | defer res.HealthResponse.Close() 38 | 39 | for res.HealthResponse.Next() { 40 | event := res.HealthResponse.Value() 41 | log.Print(event) 42 | // Handle the event 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ### Parameters 49 | 50 | | Parameter | Type | Required | Description | 51 | | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | 52 | | `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | 53 | | `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | 54 | 55 | ### Response 56 | 57 | **[*operations.CheckHealthResponse](../../models/operations/checkhealthresponse.md), error** 58 | 59 | ### Errors 60 | 61 | | Error Type | Status Code | Content Type | 62 | | ------------------ | ------------------ | ------------------ | 63 | | sdkerrors.SDKError | 4XX, 5XX | \*/\* | -------------------------------------------------------------------------------- /internal/studio/sdk/docs/sdks/suggest/README.md: -------------------------------------------------------------------------------- 1 | # Suggest 2 | (*Suggest*) 3 | 4 | ## Overview 5 | 6 | ### Available Operations 7 | 8 | * [MethodNames](#methodnames) - Suggest Method Names 9 | 10 | ## MethodNames 11 | 12 | Suggest method names for the current source. 13 | 14 | ### Example Usage 15 | 16 | ```go 17 | package main 18 | 19 | import( 20 | "context" 21 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk" 22 | "log" 23 | ) 24 | 25 | func main() { 26 | ctx := context.Background() 27 | 28 | s := sdk.New( 29 | sdk.WithSecurity(""), 30 | ) 31 | 32 | res, err := s.Suggest.MethodNames(ctx) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | if res.SuggestResponse != nil { 37 | // handle response 38 | } 39 | } 40 | ``` 41 | 42 | ### Parameters 43 | 44 | | Parameter | Type | Required | Description | 45 | | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | 46 | | `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | 47 | | `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | 48 | 49 | ### Response 50 | 51 | **[*operations.SuggestMethodNamesResponse](../../models/operations/suggestmethodnamesresponse.md), error** 52 | 53 | ### Errors 54 | 55 | | Error Type | Status Code | Content Type | 56 | | ------------------ | ------------------ | ------------------ | 57 | | sdkerrors.SDKError | 4XX, 5XX | \*/\* | -------------------------------------------------------------------------------- /internal/studio/sdk/internal/hooks/registration.go: -------------------------------------------------------------------------------- 1 | package hooks 2 | 3 | /* 4 | * This file is only ever generated once on the first generation and then is free to be modified. 5 | * Any hooks you wish to add should be registered in the InitHooks function. Feel free to define them 6 | * in this file or in separate files in the hooks package. 7 | */ 8 | 9 | func initHooks(h *Hooks) { 10 | // Add hooks by calling h.register{SDKInit/BeforeRequest/AfterSuccess/AfterError}Hook 11 | // with an instance of a hook that implements that specific Hook interface 12 | // Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance 13 | } 14 | -------------------------------------------------------------------------------- /internal/studio/sdk/internal/utils/contenttype.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package utils 4 | 5 | import ( 6 | "fmt" 7 | "mime" 8 | "strings" 9 | ) 10 | 11 | func MatchContentType(contentType string, pattern string) bool { 12 | if contentType == "" { 13 | contentType = "application/octet-stream" 14 | } 15 | 16 | if contentType == pattern || pattern == "*" || pattern == "*/*" { 17 | return true 18 | } 19 | 20 | mediaType, _, err := mime.ParseMediaType(contentType) 21 | if err != nil { 22 | return false 23 | } 24 | 25 | if mediaType == pattern { 26 | return true 27 | } 28 | 29 | parts := strings.Split(mediaType, "/") 30 | if len(parts) == 2 { 31 | if fmt.Sprintf("%s/*", parts[0]) == pattern || fmt.Sprintf("*/%s", parts[1]) == pattern { 32 | return true 33 | } 34 | } 35 | 36 | return false 37 | } 38 | -------------------------------------------------------------------------------- /internal/studio/sdk/internal/utils/env.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package utils 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | // GetEnv returns the value of the environment variable named by the key or the defaultValue if the environment variable is not set. 10 | func GetEnv(name, defaultValue string) string { 11 | value := os.Getenv(name) 12 | if value == "" { 13 | return defaultValue 14 | } 15 | return value 16 | } 17 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/auth.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Auth struct { 6 | // Auth header 7 | AuthHeader string `json:"authHeader"` 8 | // Auth secret 9 | AuthSecret string `json:"authSecret"` 10 | } 11 | 12 | func (o *Auth) GetAuthHeader() string { 13 | if o == nil { 14 | return "" 15 | } 16 | return o.AuthHeader 17 | } 18 | 19 | func (o *Auth) GetAuthSecret() string { 20 | if o == nil { 21 | return "" 22 | } 23 | return o.AuthSecret 24 | } 25 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/codesamples.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type CodeSamples struct { 6 | // Output string 7 | Output *string `json:"output,omitempty"` 8 | Registry *SourceRegistry `json:"registry,omitempty"` 9 | } 10 | 11 | func (o *CodeSamples) GetOutput() *string { 12 | if o == nil { 13 | return nil 14 | } 15 | return o.Output 16 | } 17 | 18 | func (o *CodeSamples) GetRegistry() *SourceRegistry { 19 | if o == nil { 20 | return nil 21 | } 22 | return o.Registry 23 | } 24 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/diagnostic.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Diagnostic struct { 6 | // Message describing the issue 7 | Message string `json:"message"` 8 | // Severity 9 | Severity string `json:"severity"` 10 | // Line number 11 | Line *int64 `json:"line,omitempty"` 12 | // Schema path to the issue 13 | Path []string `json:"path,omitempty"` 14 | // Issue type 15 | Type string `json:"type"` 16 | // Help message for how to fix the issue 17 | HelpMessage *string `json:"helpMessage,omitempty"` 18 | } 19 | 20 | func (o *Diagnostic) GetMessage() string { 21 | if o == nil { 22 | return "" 23 | } 24 | return o.Message 25 | } 26 | 27 | func (o *Diagnostic) GetSeverity() string { 28 | if o == nil { 29 | return "" 30 | } 31 | return o.Severity 32 | } 33 | 34 | func (o *Diagnostic) GetLine() *int64 { 35 | if o == nil { 36 | return nil 37 | } 38 | return o.Line 39 | } 40 | 41 | func (o *Diagnostic) GetPath() []string { 42 | if o == nil { 43 | return nil 44 | } 45 | return o.Path 46 | } 47 | 48 | func (o *Diagnostic) GetType() string { 49 | if o == nil { 50 | return "" 51 | } 52 | return o.Type 53 | } 54 | 55 | func (o *Diagnostic) GetHelpMessage() *string { 56 | if o == nil { 57 | return nil 58 | } 59 | return o.HelpMessage 60 | } 61 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/document.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Document struct { 6 | // Document location 7 | Location string `json:"location"` 8 | Auth *Auth `json:"auth,omitempty"` 9 | } 10 | 11 | func (o *Document) GetLocation() string { 12 | if o == nil { 13 | return "" 14 | } 15 | return o.Location 16 | } 17 | 18 | func (o *Document) GetAuth() *Auth { 19 | if o == nil { 20 | return nil 21 | } 22 | return o.Auth 23 | } 24 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/fallbackcodesamples.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type FallbackCodeSamples struct { 6 | } 7 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/healthresponse.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Data struct { 6 | // Status of the CLI 7 | Status *string `json:"status,omitempty"` 8 | } 9 | 10 | func (o *Data) GetStatus() *string { 11 | if o == nil { 12 | return nil 13 | } 14 | return o.Status 15 | } 16 | 17 | // HealthResponse - Successful response 18 | type HealthResponse struct { 19 | Data *Data `json:"data,omitempty"` 20 | } 21 | 22 | func (o *HealthResponse) GetData() *Data { 23 | if o == nil { 24 | return nil 25 | } 26 | return o.Data 27 | } 28 | 29 | func (o HealthResponse) GetEventEncoding(event string) (string, error) { 30 | return "application/json", nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/httpmetadata.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | import ( 6 | "net/http" 7 | ) 8 | 9 | type HTTPMetadata struct { 10 | // Raw HTTP response; suitable for custom response parsing 11 | Response *http.Response `json:"-"` 12 | // Raw HTTP request; suitable for debugging 13 | Request *http.Request `json:"-"` 14 | } 15 | 16 | func (o *HTTPMetadata) GetResponse() *http.Response { 17 | if o == nil { 18 | return nil 19 | } 20 | return o.Response 21 | } 22 | 23 | func (o *HTTPMetadata) GetRequest() *http.Request { 24 | if o == nil { 25 | return nil 26 | } 27 | return o.Request 28 | } 29 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/overlay.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | // OverlayDocument - Document information 6 | type OverlayDocument struct { 7 | // Document location 8 | Location string `json:"location"` 9 | Auth *Auth `json:"auth,omitempty"` 10 | // Document contents 11 | Contents *string `json:"contents,omitempty"` 12 | } 13 | 14 | func (o *OverlayDocument) GetLocation() string { 15 | if o == nil { 16 | return "" 17 | } 18 | return o.Location 19 | } 20 | 21 | func (o *OverlayDocument) GetAuth() *Auth { 22 | if o == nil { 23 | return nil 24 | } 25 | return o.Auth 26 | } 27 | 28 | func (o *OverlayDocument) GetContents() *string { 29 | if o == nil { 30 | return nil 31 | } 32 | return o.Contents 33 | } 34 | 35 | type Overlay struct { 36 | FallbackCodeSamples *FallbackCodeSamples `json:"fallbackCodeSamples,omitempty"` 37 | // Document information 38 | Document *OverlayDocument `json:"document,omitempty"` 39 | } 40 | 41 | func (o *Overlay) GetFallbackCodeSamples() *FallbackCodeSamples { 42 | if o == nil { 43 | return nil 44 | } 45 | return o.FallbackCodeSamples 46 | } 47 | 48 | func (o *Overlay) GetDocument() *OverlayDocument { 49 | if o == nil { 50 | return nil 51 | } 52 | return o.Document 53 | } 54 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/overlaycomparerequestbody.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type OverlayCompareRequestBody struct { 6 | // The first yaml file 7 | Before string `json:"before"` 8 | // The second yaml file 9 | After string `json:"after"` 10 | } 11 | 12 | func (o *OverlayCompareRequestBody) GetBefore() string { 13 | if o == nil { 14 | return "" 15 | } 16 | return o.Before 17 | } 18 | 19 | func (o *OverlayCompareRequestBody) GetAfter() string { 20 | if o == nil { 21 | return "" 22 | } 23 | return o.After 24 | } 25 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/overlaycompareresponse.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | // OverlayCompareResponse - Successful response 6 | type OverlayCompareResponse struct { 7 | // The studio modifications overlay contents - this should be an overlay YAML document 8 | Overlay string `json:"overlay"` 9 | } 10 | 11 | func (o *OverlayCompareResponse) GetOverlay() string { 12 | if o == nil { 13 | return "" 14 | } 15 | return o.Overlay 16 | } 17 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/publishing.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Publishing struct { 6 | } 7 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/runrequestbody.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type RunRequestBody struct { 6 | // The studio modifications overlay contents - this should be an overlay YAML document 7 | Overlay string `json:"overlay"` 8 | // The input spec for the source 9 | Input string `json:"input"` 10 | // Map of target specific inputs keyed on target name 11 | // Only present if a target input is modified 12 | // 13 | Targets map[string]TargetSpecificInputs `json:"targets"` 14 | } 15 | 16 | func (o *RunRequestBody) GetOverlay() string { 17 | if o == nil { 18 | return "" 19 | } 20 | return o.Overlay 21 | } 22 | 23 | func (o *RunRequestBody) GetInput() string { 24 | if o == nil { 25 | return "" 26 | } 27 | return o.Input 28 | } 29 | 30 | func (o *RunRequestBody) GetTargets() map[string]TargetSpecificInputs { 31 | if o == nil { 32 | return map[string]TargetSpecificInputs{} 33 | } 34 | return o.Targets 35 | } 36 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/runresponse.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | // RunResponse - Successful response 6 | type RunResponse struct { 7 | // Type of the stream 8 | Event string `json:"event"` 9 | // Map of target run summaries 10 | Data RunResponseData `json:"data"` 11 | } 12 | 13 | func (o *RunResponse) GetEvent() string { 14 | if o == nil { 15 | return "" 16 | } 17 | return o.Event 18 | } 19 | 20 | func (o *RunResponse) GetData() RunResponseData { 21 | if o == nil { 22 | return RunResponseData{} 23 | } 24 | return o.Data 25 | } 26 | 27 | func (o RunResponse) GetEventEncoding(event string) (string, error) { 28 | return "application/json", nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/runresponsedata.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | // RunResponseData - Map of target run summaries 6 | type RunResponseData struct { 7 | // Step of the run 8 | Step string `json:"step"` 9 | // Whether the run was partial 10 | IsPartial bool `json:"isPartial"` 11 | // Link to the linting report 12 | LintingReportLink *string `json:"lintingReportLink,omitempty"` 13 | SourceResult SourceResponseData `json:"sourceResult"` 14 | // Map of target results 15 | TargetResults map[string]TargetRunSummary `json:"targetResults"` 16 | Workflow Workflow `json:"workflow"` 17 | // Working directory 18 | WorkingDirectory string `json:"workingDirectory"` 19 | // Time taken to run the workflow in milliseconds 20 | Took int64 `json:"took"` 21 | // Error message if the run failed 22 | Error *string `json:"error,omitempty"` 23 | } 24 | 25 | func (o *RunResponseData) GetStep() string { 26 | if o == nil { 27 | return "" 28 | } 29 | return o.Step 30 | } 31 | 32 | func (o *RunResponseData) GetIsPartial() bool { 33 | if o == nil { 34 | return false 35 | } 36 | return o.IsPartial 37 | } 38 | 39 | func (o *RunResponseData) GetLintingReportLink() *string { 40 | if o == nil { 41 | return nil 42 | } 43 | return o.LintingReportLink 44 | } 45 | 46 | func (o *RunResponseData) GetSourceResult() SourceResponseData { 47 | if o == nil { 48 | return SourceResponseData{} 49 | } 50 | return o.SourceResult 51 | } 52 | 53 | func (o *RunResponseData) GetTargetResults() map[string]TargetRunSummary { 54 | if o == nil { 55 | return map[string]TargetRunSummary{} 56 | } 57 | return o.TargetResults 58 | } 59 | 60 | func (o *RunResponseData) GetWorkflow() Workflow { 61 | if o == nil { 62 | return Workflow{} 63 | } 64 | return o.Workflow 65 | } 66 | 67 | func (o *RunResponseData) GetWorkingDirectory() string { 68 | if o == nil { 69 | return "" 70 | } 71 | return o.WorkingDirectory 72 | } 73 | 74 | func (o *RunResponseData) GetTook() int64 { 75 | if o == nil { 76 | return 0 77 | } 78 | return o.Took 79 | } 80 | 81 | func (o *RunResponseData) GetError() *string { 82 | if o == nil { 83 | return nil 84 | } 85 | return o.Error 86 | } 87 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/security.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Security struct { 6 | Secret string `security:"scheme,type=apiKey,subtype=header,name=x-secret-key"` 7 | } 8 | 9 | func (o *Security) GetSecret() string { 10 | if o == nil { 11 | return "" 12 | } 13 | return o.Secret 14 | } 15 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/source.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Source struct { 6 | // List of input documents 7 | Inputs []Document `json:"inputs,omitempty"` 8 | // List of overlays 9 | Overlays []Overlay `json:"overlays,omitempty"` 10 | // Output string 11 | Output *string `json:"output,omitempty"` 12 | // Ruleset string 13 | Ruleset *string `json:"ruleset,omitempty"` 14 | Registry *SourceRegistry `json:"registry,omitempty"` 15 | } 16 | 17 | func (o *Source) GetInputs() []Document { 18 | if o == nil { 19 | return nil 20 | } 21 | return o.Inputs 22 | } 23 | 24 | func (o *Source) GetOverlays() []Overlay { 25 | if o == nil { 26 | return nil 27 | } 28 | return o.Overlays 29 | } 30 | 31 | func (o *Source) GetOutput() *string { 32 | if o == nil { 33 | return nil 34 | } 35 | return o.Output 36 | } 37 | 38 | func (o *Source) GetRuleset() *string { 39 | if o == nil { 40 | return nil 41 | } 42 | return o.Ruleset 43 | } 44 | 45 | func (o *Source) GetRegistry() *SourceRegistry { 46 | if o == nil { 47 | return nil 48 | } 49 | return o.Registry 50 | } 51 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/sourceregistry.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type SourceRegistry struct { 6 | // Source registry location 7 | Location *string `json:"location,omitempty"` 8 | // List of tags 9 | Tags []string `json:"tags,omitempty"` 10 | } 11 | 12 | func (o *SourceRegistry) GetLocation() *string { 13 | if o == nil { 14 | return nil 15 | } 16 | return o.Location 17 | } 18 | 19 | func (o *SourceRegistry) GetTags() []string { 20 | if o == nil { 21 | return nil 22 | } 23 | return o.Tags 24 | } 25 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/sourceresponsedata.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type SourceResponseData struct { 6 | // Source ID in the workflow file 7 | SourceID string `json:"sourceID"` 8 | // The merged input specs for the source 9 | Input string `json:"input"` 10 | // Studio modifications overlay contents (could be empty string) 11 | Overlay string `json:"overlay"` 12 | // Studio modifications overlay path 13 | OverlayPath string `json:"overlayPath"` 14 | // Result of running the source in the workflow 15 | Output string `json:"output"` 16 | // List of validation errors 17 | Diagnosis []Diagnostic `json:"diagnosis,omitempty"` 18 | } 19 | 20 | func (o *SourceResponseData) GetSourceID() string { 21 | if o == nil { 22 | return "" 23 | } 24 | return o.SourceID 25 | } 26 | 27 | func (o *SourceResponseData) GetInput() string { 28 | if o == nil { 29 | return "" 30 | } 31 | return o.Input 32 | } 33 | 34 | func (o *SourceResponseData) GetOverlay() string { 35 | if o == nil { 36 | return "" 37 | } 38 | return o.Overlay 39 | } 40 | 41 | func (o *SourceResponseData) GetOverlayPath() string { 42 | if o == nil { 43 | return "" 44 | } 45 | return o.OverlayPath 46 | } 47 | 48 | func (o *SourceResponseData) GetOutput() string { 49 | if o == nil { 50 | return "" 51 | } 52 | return o.Output 53 | } 54 | 55 | func (o *SourceResponseData) GetDiagnosis() []Diagnostic { 56 | if o == nil { 57 | return nil 58 | } 59 | return o.Diagnosis 60 | } 61 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/suggestresponse.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | // SuggestResponse - Successful response 6 | type SuggestResponse struct { 7 | // The studio modifications overlay contents - this should be an overlay YAML document 8 | Overlay string `json:"overlay"` 9 | } 10 | 11 | func (o *SuggestResponse) GetOverlay() string { 12 | if o == nil { 13 | return "" 14 | } 15 | return o.Overlay 16 | } 17 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/target.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Target struct { 6 | // Target language 7 | Target string `json:"target"` 8 | // Source ID 9 | Source string `json:"source"` 10 | // Output string 11 | Output *string `json:"output,omitempty"` 12 | Publishing *Publishing `json:"publishing,omitempty"` 13 | CodeSamples *CodeSamples `json:"codeSamples,omitempty"` 14 | } 15 | 16 | func (o *Target) GetTarget() string { 17 | if o == nil { 18 | return "" 19 | } 20 | return o.Target 21 | } 22 | 23 | func (o *Target) GetSource() string { 24 | if o == nil { 25 | return "" 26 | } 27 | return o.Source 28 | } 29 | 30 | func (o *Target) GetOutput() *string { 31 | if o == nil { 32 | return nil 33 | } 34 | return o.Output 35 | } 36 | 37 | func (o *Target) GetPublishing() *Publishing { 38 | if o == nil { 39 | return nil 40 | } 41 | return o.Publishing 42 | } 43 | 44 | func (o *Target) GetCodeSamples() *CodeSamples { 45 | if o == nil { 46 | return nil 47 | } 48 | return o.CodeSamples 49 | } 50 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/targetrunsummary.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type TargetRunSummary struct { 6 | // Contents of the README file for this target 7 | Readme string `json:"readme"` 8 | // Contents of the gen.yaml file for this target 9 | GenYaml string `json:"gen_yaml"` 10 | // The path to the gen.yaml file for this target 11 | GenYamlPath *string `json:"gen_yaml_path,omitempty"` 12 | // Output directory for this target 13 | OutputDirectory string `json:"output_directory"` 14 | // Language for this target 15 | Language string `json:"language"` 16 | // Source ID in the workflow file 17 | SourceID string `json:"sourceID"` 18 | // Target ID in the workflow file 19 | TargetID string `json:"targetID"` 20 | } 21 | 22 | func (o *TargetRunSummary) GetReadme() string { 23 | if o == nil { 24 | return "" 25 | } 26 | return o.Readme 27 | } 28 | 29 | func (o *TargetRunSummary) GetGenYaml() string { 30 | if o == nil { 31 | return "" 32 | } 33 | return o.GenYaml 34 | } 35 | 36 | func (o *TargetRunSummary) GetGenYamlPath() *string { 37 | if o == nil { 38 | return nil 39 | } 40 | return o.GenYamlPath 41 | } 42 | 43 | func (o *TargetRunSummary) GetOutputDirectory() string { 44 | if o == nil { 45 | return "" 46 | } 47 | return o.OutputDirectory 48 | } 49 | 50 | func (o *TargetRunSummary) GetLanguage() string { 51 | if o == nil { 52 | return "" 53 | } 54 | return o.Language 55 | } 56 | 57 | func (o *TargetRunSummary) GetSourceID() string { 58 | if o == nil { 59 | return "" 60 | } 61 | return o.SourceID 62 | } 63 | 64 | func (o *TargetRunSummary) GetTargetID() string { 65 | if o == nil { 66 | return "" 67 | } 68 | return o.TargetID 69 | } 70 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/targetspecificinputs.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type TargetSpecificInputs struct { 6 | // New contents of the gen.yaml file for this target 7 | Config string `json:"config"` 8 | } 9 | 10 | func (o *TargetSpecificInputs) GetConfig() string { 11 | if o == nil { 12 | return "" 13 | } 14 | return o.Config 15 | } 16 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/components/workflow.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package components 4 | 5 | type Workflow struct { 6 | // Workflow version 7 | Version string `json:"version"` 8 | // Speakeasy version 9 | SpeakeasyVersion string `json:"speakeasyVersion"` 10 | // Map of sources 11 | Sources map[string]Source `json:"sources"` 12 | // Map of targets 13 | Targets map[string]Target `json:"targets"` 14 | } 15 | 16 | func (o *Workflow) GetVersion() string { 17 | if o == nil { 18 | return "" 19 | } 20 | return o.Version 21 | } 22 | 23 | func (o *Workflow) GetSpeakeasyVersion() string { 24 | if o == nil { 25 | return "" 26 | } 27 | return o.SpeakeasyVersion 28 | } 29 | 30 | func (o *Workflow) GetSources() map[string]Source { 31 | if o == nil { 32 | return map[string]Source{} 33 | } 34 | return o.Sources 35 | } 36 | 37 | func (o *Workflow) GetTargets() map[string]Target { 38 | if o == nil { 39 | return map[string]Target{} 40 | } 41 | return o.Targets 42 | } 43 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/checkhealth.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/types/stream" 8 | ) 9 | 10 | type CheckHealthResponse struct { 11 | HTTPMeta components.HTTPMetadata `json:"-"` 12 | // Successful response 13 | HealthResponse *stream.EventStream[components.HealthResponse] 14 | } 15 | 16 | func (o *CheckHealthResponse) GetHTTPMeta() components.HTTPMetadata { 17 | if o == nil { 18 | return components.HTTPMetadata{} 19 | } 20 | return o.HTTPMeta 21 | } 22 | 23 | func (o *CheckHealthResponse) GetHealthResponse() *stream.EventStream[components.HealthResponse] { 24 | if o == nil { 25 | return nil 26 | } 27 | return o.HealthResponse 28 | } 29 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/exit.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | ) 8 | 9 | type ExitResponse struct { 10 | HTTPMeta components.HTTPMetadata `json:"-"` 11 | } 12 | 13 | func (o *ExitResponse) GetHTTPMeta() components.HTTPMetadata { 14 | if o == nil { 15 | return components.HTTPMetadata{} 16 | } 17 | return o.HTTPMeta 18 | } 19 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/generateoverlay.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | ) 8 | 9 | type GenerateOverlayResponse struct { 10 | HTTPMeta components.HTTPMetadata `json:"-"` 11 | // Successful response 12 | OverlayCompareResponse *components.OverlayCompareResponse 13 | } 14 | 15 | func (o *GenerateOverlayResponse) GetHTTPMeta() components.HTTPMetadata { 16 | if o == nil { 17 | return components.HTTPMetadata{} 18 | } 19 | return o.HTTPMeta 20 | } 21 | 22 | func (o *GenerateOverlayResponse) GetOverlayCompareResponse() *components.OverlayCompareResponse { 23 | if o == nil { 24 | return nil 25 | } 26 | return o.OverlayCompareResponse 27 | } 28 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/getrun.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/types/stream" 8 | ) 9 | 10 | type GetRunResponse struct { 11 | HTTPMeta components.HTTPMetadata `json:"-"` 12 | // Successful response 13 | RunResponse *stream.EventStream[components.RunResponse] 14 | } 15 | 16 | func (o *GetRunResponse) GetHTTPMeta() components.HTTPMetadata { 17 | if o == nil { 18 | return components.HTTPMetadata{} 19 | } 20 | return o.HTTPMeta 21 | } 22 | 23 | func (o *GetRunResponse) GetRunResponse() *stream.EventStream[components.RunResponse] { 24 | if o == nil { 25 | return nil 26 | } 27 | return o.RunResponse 28 | } 29 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/run.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/types/stream" 8 | ) 9 | 10 | type RunResponse struct { 11 | HTTPMeta components.HTTPMetadata `json:"-"` 12 | // Successful response 13 | RunResponse *stream.EventStream[components.RunResponse] 14 | } 15 | 16 | func (o *RunResponse) GetHTTPMeta() components.HTTPMetadata { 17 | if o == nil { 18 | return components.HTTPMetadata{} 19 | } 20 | return o.HTTPMeta 21 | } 22 | 23 | func (o *RunResponse) GetRunResponse() *stream.EventStream[components.RunResponse] { 24 | if o == nil { 25 | return nil 26 | } 27 | return o.RunResponse 28 | } 29 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/suggestmethodnames.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | ) 8 | 9 | type SuggestMethodNamesResponse struct { 10 | HTTPMeta components.HTTPMetadata `json:"-"` 11 | // Successful response 12 | SuggestResponse *components.SuggestResponse 13 | } 14 | 15 | func (o *SuggestMethodNamesResponse) GetHTTPMeta() components.HTTPMetadata { 16 | if o == nil { 17 | return components.HTTPMetadata{} 18 | } 19 | return o.HTTPMeta 20 | } 21 | 22 | func (o *SuggestMethodNamesResponse) GetSuggestResponse() *components.SuggestResponse { 23 | if o == nil { 24 | return nil 25 | } 26 | return o.SuggestResponse 27 | } 28 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/operations/updatesource.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package operations 4 | 5 | import ( 6 | "github.com/speakeasy-api/speakeasy/internal/studio/sdk/models/components" 7 | ) 8 | 9 | type UpdateSourceRequestBody struct { 10 | // The studio modifications overlay contents - this should be an overlay YAML document 11 | Overlay *string `json:"overlay,omitempty"` 12 | // The input spec for the source 13 | Input *string `json:"input,omitempty"` 14 | } 15 | 16 | func (o *UpdateSourceRequestBody) GetOverlay() *string { 17 | if o == nil { 18 | return nil 19 | } 20 | return o.Overlay 21 | } 22 | 23 | func (o *UpdateSourceRequestBody) GetInput() *string { 24 | if o == nil { 25 | return nil 26 | } 27 | return o.Input 28 | } 29 | 30 | type UpdateSourceResponse struct { 31 | HTTPMeta components.HTTPMetadata `json:"-"` 32 | // Successful response 33 | SourceResult *components.SourceResponseData 34 | } 35 | 36 | func (o *UpdateSourceResponse) GetHTTPMeta() components.HTTPMetadata { 37 | if o == nil { 38 | return components.HTTPMetadata{} 39 | } 40 | return o.HTTPMeta 41 | } 42 | 43 | func (o *UpdateSourceResponse) GetSourceResponse() *components.SourceResponseData { 44 | if o == nil { 45 | return nil 46 | } 47 | return o.SourceResult 48 | } 49 | -------------------------------------------------------------------------------- /internal/studio/sdk/models/sdkerrors/sdkerror.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package sdkerrors 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | type SDKError struct { 11 | Message string 12 | StatusCode int 13 | Body string 14 | RawResponse *http.Response 15 | } 16 | 17 | var _ error = &SDKError{} 18 | 19 | func NewSDKError(message string, statusCode int, body string, httpRes *http.Response) *SDKError { 20 | return &SDKError{ 21 | Message: message, 22 | StatusCode: statusCode, 23 | Body: body, 24 | RawResponse: httpRes, 25 | } 26 | } 27 | 28 | func (e *SDKError) Error() string { 29 | body := "" 30 | if len(e.Body) > 0 { 31 | body = fmt.Sprintf("\n%s", e.Body) 32 | } 33 | 34 | return fmt.Sprintf("%s: Status %d%s", e.Message, e.StatusCode, body) 35 | } 36 | -------------------------------------------------------------------------------- /internal/studio/sdk/types/bigint.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package types 4 | 5 | import ( 6 | "fmt" 7 | "math/big" 8 | ) 9 | 10 | // MustNewBigIntFromString returns an instance of big.Int from a string 11 | // The string is assumed to be base 10 and if it is not a valid big.Int 12 | // then the function panics. 13 | // Avoid using this function in production code. 14 | func MustNewBigIntFromString(s string) *big.Int { 15 | i, ok := new(big.Int).SetString(s, 10) 16 | if !ok { 17 | panic(fmt.Errorf("failed to parse string as big.Int")) 18 | } 19 | 20 | return i 21 | } 22 | -------------------------------------------------------------------------------- /internal/studio/sdk/types/date.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package types 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // Date is a wrapper around time.Time that allows for JSON marshaling a date string formatted as "2006-01-02". 13 | type Date struct { 14 | time.Time 15 | } 16 | 17 | var ( 18 | _ json.Marshaler = &Date{} 19 | _ json.Unmarshaler = &Date{} 20 | _ fmt.Stringer = &Date{} 21 | ) 22 | 23 | // NewDate returns an instance of Date from a time.Time. 24 | func NewDate(t time.Time) *Date { 25 | d := DateFromTime(t) 26 | return &d 27 | } 28 | 29 | // DateFromTime returns a Date from a time.Time. 30 | func DateFromTime(t time.Time) Date { 31 | return Date{t} 32 | } 33 | 34 | // NewDateFromString returns an instance of Date from a string formatted as "2006-01-02". 35 | func NewDateFromString(str string) (*Date, error) { 36 | d, err := DateFromString(str) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &d, nil 42 | } 43 | 44 | // DateFromString returns a Date from a string formatted as "2006-01-02". 45 | func DateFromString(str string) (Date, error) { 46 | var d Date 47 | var err error 48 | 49 | d.Time, err = time.Parse("2006-01-02", str) 50 | return d, err 51 | } 52 | 53 | // MustNewDateFromString returns an instance of Date from a string formatted as "2006-01-02" or panics. 54 | // Avoid using this function in production code. 55 | func MustNewDateFromString(str string) *Date { 56 | d := MustDateFromString(str) 57 | return &d 58 | } 59 | 60 | // MustDateFromString returns a Date from a string formatted as "2006-01-02" or panics. 61 | // Avoid using this function in production code. 62 | func MustDateFromString(str string) Date { 63 | d, err := DateFromString(str) 64 | if err != nil { 65 | panic(err) 66 | } 67 | return d 68 | } 69 | 70 | func (d Date) GetTime() time.Time { 71 | return d.Time 72 | } 73 | 74 | func (d Date) MarshalJSON() ([]byte, error) { 75 | return []byte(fmt.Sprintf(`"%s"`, d.Time.Format("2006-01-02"))), nil 76 | } 77 | 78 | func (d *Date) UnmarshalJSON(data []byte) error { 79 | var err error 80 | 81 | str := string(data) 82 | str = strings.Trim(str, `"`) 83 | 84 | d.Time, err = time.Parse("2006-01-02", str) 85 | return err 86 | } 87 | 88 | func (d Date) String() string { 89 | return d.Time.Format("2006-01-02") 90 | } 91 | -------------------------------------------------------------------------------- /internal/studio/sdk/types/datetime.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package types 4 | 5 | import "time" 6 | 7 | // MustTimeFromString returns a time.Time from a string formatted as "2006-01-02T15:04:05Z07:00" or panics. 8 | // Avoid using this function in production code. 9 | func MustTimeFromString(str string) time.Time { 10 | t, err := time.Parse(time.RFC3339, str) 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | return t 16 | } 17 | 18 | // MustNewTimeFromString returns an instance of time.Time from a string formatted as "2006-01-02T15:04:05Z07:00" or panics. 19 | // Avoid using this function in production code. 20 | func MustNewTimeFromString(str string) *time.Time { 21 | t := MustTimeFromString(str) 22 | return &t 23 | } 24 | -------------------------------------------------------------------------------- /internal/studio/sdk/types/decimal.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package types 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/ericlagergren/decimal" 9 | ) 10 | 11 | // MustNewDecimalFromString returns an instance of Decimal from a string 12 | // Avoid using this function in production code. 13 | func MustNewDecimalFromString(s string) *decimal.Big { 14 | d, ok := new(decimal.Big).SetString(s) 15 | if !ok { 16 | panic(fmt.Errorf("failed to parse string as decimal.Big")) 17 | } 18 | 19 | return d 20 | } 21 | -------------------------------------------------------------------------------- /internal/studio/sdk/types/pointers.go: -------------------------------------------------------------------------------- 1 | // Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 2 | 3 | package types 4 | 5 | func String(s string) *string { return &s } 6 | func Bool(b bool) *bool { return &b } 7 | func Int(i int) *int { return &i } 8 | func Int64(i int64) *int64 { return &i } 9 | func Float32(f float32) *float32 { return &f } 10 | func Float64(f float64) *float64 { return &f } 11 | func Pointer[T any](v T) *T { return &v } 12 | -------------------------------------------------------------------------------- /internal/suggest/diagnose.go: -------------------------------------------------------------------------------- 1 | package suggest 2 | 3 | import ( 4 | "context" 5 | "github.com/speakeasy-api/speakeasy-core/openapi" 6 | "github.com/speakeasy-api/speakeasy-core/suggestions" 7 | "github.com/speakeasy-api/speakeasy/internal/schemas" 8 | ) 9 | 10 | func Diagnose(ctx context.Context, schemaPath string) (suggestions.Diagnosis, error) { 11 | data, _, _, err := schemas.LoadDocument(ctx, schemaPath) 12 | if err != nil { 13 | return nil, err 14 | } 15 | summary, err := openapi.GetOASSummary(data, schemaPath) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return suggestions.Diagnose(ctx, *summary) 21 | } 22 | 23 | func ShouldSuggest(d suggestions.Diagnosis) bool { 24 | return len(d) > 0 25 | } 26 | -------------------------------------------------------------------------------- /internal/suggest/errorCodes/diagnose.go: -------------------------------------------------------------------------------- 1 | package errorCodes 2 | 3 | import ( 4 | "fmt" 5 | v3 "github.com/pb33f/libopenapi/datamodel/high/v3" 6 | "github.com/speakeasy-api/speakeasy-core/openapi" 7 | "github.com/speakeasy-api/speakeasy-core/suggestions" 8 | ) 9 | 10 | var errorGroupsDiagnose = initErrorGroups() 11 | 12 | func Diagnose(document v3.Document) suggestions.Diagnosis { 13 | diagnosis := suggestions.Diagnosis{} 14 | 15 | for op := range openapi.IterateOperations(document) { 16 | method, path, operation := op.Method, op.Path, op.Operation 17 | 18 | schemaPath := openapi.GetOperationSchemaPath(path, method) 19 | 20 | codes := operation.Responses.Codes 21 | if codes == nil { 22 | diagnosis.Add(suggestions.MissingErrorCodes, suggestions.Diagnostic{ 23 | SchemaPath: schemaPath, 24 | Message: fmt.Sprintf("Operation %s (%s %s) has no responses defined!", operation.OperationId, method, path), 25 | }) 26 | } 27 | 28 | missingCodes := getMissingErrorCodes(operation) 29 | if len(missingCodes) > 0 { 30 | diagnosis.Add(suggestions.MissingErrorCodes, suggestions.Diagnostic{ 31 | SchemaPath: schemaPath, 32 | Message: fmt.Sprintf("Operation %s (%s %s) is missing definitions for %d recommended error codes", operation.OperationId, method, path, len(missingCodes)), 33 | }) 34 | } 35 | } 36 | 37 | return diagnosis 38 | } 39 | 40 | func getMissingErrorCodes(operation *v3.Operation) []string { 41 | var missingCodes []string 42 | 43 | for _, code := range errorGroupsDiagnose.AllCodes() { 44 | if response, _ := getResponseForCode(operation.Responses.Codes, code); response == nil { 45 | missingCodes = append(missingCodes, code) 46 | } 47 | } 48 | 49 | return missingCodes 50 | } 51 | -------------------------------------------------------------------------------- /internal/suggest/errorCodes/errorCodes_test.go: -------------------------------------------------------------------------------- 1 | package errorCodes_test 2 | 3 | import ( 4 | "context" 5 | "github.com/speakeasy-api/speakeasy-core/suggestions" 6 | "github.com/speakeasy-api/speakeasy/internal/schemas" 7 | "github.com/speakeasy-api/speakeasy/internal/suggest/errorCodes" 8 | "github.com/stretchr/testify/require" 9 | "gopkg.in/yaml.v3" 10 | "os" 11 | "testing" 12 | ) 13 | 14 | func TestBuildErrorCodesOverlay(t *testing.T) { 15 | type args struct { 16 | name, in, out string 17 | } 18 | toTest := []args{ 19 | {"Simple petstore", "testData/petstore.yaml", "testData/petstore_expected.yaml"}, 20 | {"Reuse 4XX code", "testData/reuse4xx.yaml", "testData/reuse4xx_expected.yaml"}, 21 | {"Simple case -- add all missing", "testData/simple.yaml", "testData/simple_expected.yaml"}, 22 | {"Name conflict in added schema", "testData/nameConflict.yaml", "testData/nameConflict_expected.yaml"}, 23 | } 24 | 25 | for _, tt := range toTest { 26 | t.Run(tt.name, func(t *testing.T) { 27 | ctx := context.Background() 28 | 29 | overlay, err := errorCodes.BuildErrorCodesOverlay(ctx, tt.in) 30 | 31 | _, _, model, err := schemas.LoadDocument(ctx, tt.in) 32 | require.NoError(t, err) 33 | root := model.Index.GetRootNode() 34 | err = overlay.ApplyTo(root) 35 | require.NoError(t, err) 36 | 37 | // Read the expected YAML file 38 | expectedBytes, err := os.ReadFile(tt.out) 39 | require.NoError(t, err) 40 | 41 | // Convert root to YAML 42 | actualBytes, err := yaml.Marshal(root) 43 | require.NoError(t, err) 44 | 45 | // Compare the actual and expected YAML 46 | require.YAMLEq(t, string(expectedBytes), string(actualBytes)) 47 | }) 48 | } 49 | } 50 | 51 | func TestDiagnose(t *testing.T) { 52 | type args struct { 53 | name, schema string 54 | expectedCount int 55 | } 56 | toTest := []args{ 57 | {"Most errors missing", "testData/simple.yaml", 3}, 58 | {"Already defined error codes", "testData/simple_expected.yaml", 0}, 59 | } 60 | 61 | for _, tt := range toTest { 62 | t.Run(tt.name, func(t *testing.T) { 63 | ctx := context.Background() 64 | _, _, model, err := schemas.LoadDocument(ctx, tt.schema) 65 | require.NoError(t, err) 66 | 67 | diagnosis := errorCodes.Diagnose(model.Model) 68 | if tt.expectedCount == 0 { 69 | require.Len(t, diagnosis, 0) 70 | return 71 | } 72 | require.Len(t, diagnosis, 1) 73 | 74 | diagnostics, ok := diagnosis[suggestions.MissingErrorCodes] 75 | require.True(t, ok) 76 | 77 | require.Len(t, diagnostics, tt.expectedCount) 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /internal/suggest/errorCodes/errorGroups.go: -------------------------------------------------------------------------------- 1 | package errorCodes 2 | 3 | import ( 4 | "slices" 5 | 6 | v3 "github.com/pb33f/libopenapi/datamodel/high/v3" 7 | "github.com/speakeasy-api/speakeasy-core/openapi" 8 | ) 9 | 10 | type errorGroup struct { 11 | name string 12 | codes []string 13 | description string 14 | schemaName, responseName string 15 | } 16 | type errorGroupSlice []errorGroup 17 | 18 | func initErrorGroups() errorGroupSlice { 19 | return errorGroupSlice{ 20 | { 21 | name: "BadRequest", 22 | codes: []string{"400", "422"}, 23 | description: "Invalid request", 24 | schemaName: "BadRequest", 25 | responseName: "BadRequest", 26 | }, 27 | { 28 | name: "Unauthorized", 29 | codes: []string{"401", "403"}, 30 | description: "Permission denied or not authenticated", 31 | schemaName: "Unauthorized", 32 | responseName: "Unauthorized", 33 | }, 34 | { 35 | name: "NotFound", 36 | codes: []string{"404"}, 37 | description: "Not found", 38 | schemaName: "NotFound", 39 | responseName: "NotFound", 40 | }, 41 | { 42 | name: "RateLimited", 43 | codes: []string{"429"}, 44 | description: "Rate limit exceeded", 45 | schemaName: "RateLimited", 46 | responseName: "RateLimited", 47 | }, 48 | } 49 | } 50 | 51 | func (e errorGroupSlice) FindCode(code string) errorGroup { 52 | for _, group := range e { 53 | if slices.Contains(group.codes, code) { 54 | return group 55 | } 56 | } 57 | return errorGroup{} 58 | } 59 | 60 | func (e errorGroupSlice) AllCodes() []string { 61 | var codes []string 62 | for _, group := range e { 63 | codes = append(codes, group.codes...) 64 | } 65 | return codes 66 | } 67 | 68 | func (e errorGroupSlice) DeduplicateComponentNames(document v3.Document) { 69 | var schemaNames []string 70 | for s := range openapi.IterateSchemas(document) { 71 | schemaNames = append(schemaNames, s.Name) 72 | } 73 | 74 | var responseNames []string 75 | for r := range openapi.IterateResponses(document) { 76 | responseNames = append(responseNames, r.Name) 77 | } 78 | 79 | for i, group := range e { 80 | e[i].responseName = findUnusedName(group.responseName, responseNames) 81 | e[i].schemaName = findUnusedName(group.schemaName, schemaNames) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /internal/testcmd/runner_opt.go: -------------------------------------------------------------------------------- 1 | package testcmd 2 | 3 | // RunnerOpt is a function that modifies a Runner. 4 | type RunnerOpt func(*Runner) 5 | 6 | // WithDisableMockserver is an option that skips starting the target testing 7 | // mock API server. 8 | func WithDisableMockserver() RunnerOpt { 9 | return func(r *Runner) { 10 | r.disableMockserver = true 11 | } 12 | } 13 | 14 | // WithVerboseOutput is an option that enables verbose output. 15 | func WithVerboseOutput() RunnerOpt { 16 | return func(r *Runner) { 17 | r.verboseOutput = true 18 | } 19 | } 20 | 21 | // WithWorkflowTarget is an option that specifies a single workflow target to 22 | // run testing against. If passed "all", all targets will be run. 23 | func WithWorkflowTarget(workflowTarget string) RunnerOpt { 24 | if workflowTarget == "all" { 25 | workflowTarget = "" 26 | } 27 | 28 | return func(r *Runner) { 29 | r.workflowTarget = workflowTarget 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/utils/openapi.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/AlekSi/pointer" 5 | "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" 6 | "github.com/speakeasy-api/speakeasy-core/openapi" 7 | "github.com/speakeasy-api/speakeasy-core/suggestions" 8 | ) 9 | 10 | func ConvertOASSummary(summary openapi.Summary) shared.OASSummary { 11 | var operations []shared.OASOperation 12 | for _, operation := range summary.Operations { 13 | o := shared.OASOperation{ 14 | Description: operation.Description, 15 | Method: operation.Method, 16 | OperationID: operation.OperationID, 17 | Path: operation.Path, 18 | Tags: operation.Tags, 19 | } 20 | 21 | if operation.GroupOverride != "" { 22 | o.GroupOverride = pointer.ToString(operation.GroupOverride) 23 | } 24 | if operation.MethodNameOverride != "" { 25 | o.MethodNameOverride = pointer.ToString(operation.MethodNameOverride) 26 | } 27 | 28 | operations = append(operations, o) 29 | } 30 | 31 | return shared.OASSummary{ 32 | Info: shared.OASInfo{ 33 | Title: summary.Info.Title, 34 | Description: summary.Info.Description, 35 | }, 36 | Operations: operations, 37 | } 38 | 39 | } 40 | 41 | func ConvertDiagnosis(diagnosis suggestions.Diagnosis) []shared.Diagnostic { 42 | var diagnostics []shared.Diagnostic 43 | for t, ds := range diagnosis { 44 | for _, d := range ds { 45 | diagnostics = append(diagnostics, shared.Diagnostic{ 46 | Message: d.Message, 47 | Path: d.SchemaPath, 48 | Type: string(t), 49 | }) 50 | } 51 | } 52 | 53 | return diagnostics 54 | } 55 | -------------------------------------------------------------------------------- /internal/validation/customConfigValidators.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type FieldValidation = func(v any, template string) error 10 | type CustomTargetValidations = map[string]FieldValidation 11 | type CustomValidations = map[string]CustomTargetValidations 12 | 13 | var customValidations = CustomValidations{ 14 | "python": { 15 | "additionalDependencies": pythonAdditionalDependenciesValidation, 16 | }, 17 | } 18 | 19 | func getValidation(target, fieldName string) FieldValidation { 20 | if v, ok := customValidations[target]; ok { 21 | if f, ok := v[fieldName]; ok { 22 | return f 23 | } 24 | } 25 | 26 | return nil 27 | } 28 | 29 | type pythonAdditionalDependencies struct { 30 | Dependencies map[string]string 31 | ExtraDependencies map[string]map[string]string 32 | } 33 | 34 | var validPrefixes = []string{"==", ">=", ">", "~=", "<", "<=", "!=", "==="} 35 | 36 | func pythonAdditionalDependenciesValidation(v any, template string) error { 37 | j, err := json.Marshal(v) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | var ad pythonAdditionalDependencies 43 | if err := json.Unmarshal(j, &ad); err != nil { 44 | return err 45 | } 46 | 47 | // This check does not apply to the pythonv2 template 48 | if (ad.Dependencies == nil || ad.ExtraDependencies == nil) && template == "python" { 49 | return fmt.Errorf("either dependencies or extraDependencies must be provided, or the entire field should be omitted") 50 | } 51 | 52 | validateDepMap := func(m map[string]string) error { 53 | for k, v := range m { 54 | if v == "" { 55 | return fmt.Errorf("dependency %s must have a version", k) 56 | } 57 | 58 | validPrefix := false 59 | for _, prefix := range validPrefixes { 60 | if strings.HasPrefix(v, prefix) { 61 | validPrefix = true 62 | } 63 | } 64 | if !validPrefix { 65 | return fmt.Errorf("dependency %s must start with one of: %s", k, strings.Join(validPrefixes, ", ")) 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | if err := validateDepMap(ad.Dependencies); err != nil { 72 | return err 73 | } 74 | for k, v := range ad.ExtraDependencies { 75 | if err := validateDepMap(v); err != nil { 76 | return fmt.Errorf("extra dependency %s is invalid: %w", k, err) 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/KimMachineGun/automemlimit/memlimit" 7 | "github.com/speakeasy-api/speakeasy/cmd" 8 | "github.com/speakeasy-api/speakeasy/internal/env" 9 | "go.uber.org/automaxprocs/maxprocs" 10 | ) 11 | 12 | var ( 13 | version = "0.0.1" 14 | artifactArch = "linux_amd64" 15 | ) 16 | 17 | func main() { 18 | memlimit.SetGoMemLimitWithOpts() 19 | maxprocs.Set() 20 | 21 | if env.IsLocalDev() { 22 | if env.GoArch() != "" { 23 | artifactArch = env.GoArch() 24 | } else if artifactArch == "" { 25 | artifactArch = runtime.GOOS + "_" + runtime.GOARCH 26 | } 27 | } 28 | 29 | cmd.Execute(version, artifactArch) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/merge/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /pkg/overlay/testdata/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.1.0", 3 | "info": { 4 | "title": "Test", 5 | "version": "0.1.0", 6 | "summary": "Test Summary", 7 | "description": "Some test description.\nAbout our test document." 8 | }, 9 | "paths": { 10 | "/anything/selectGlobalServer": { 11 | "x-my-ignore": true, 12 | "get": { 13 | "operationId": "selectGlobalServer", 14 | "responses": { 15 | "200": { 16 | "description": "OK", 17 | "headers": { 18 | "X-Optional-Header": { 19 | "schema": { 20 | "type": "string" 21 | } 22 | } 23 | } 24 | }, 25 | "404": { 26 | "description": "Not found", 27 | "content": { 28 | "application/json": { 29 | "schema": { 30 | "$ref": "./components.yaml#/components/schemas/Products" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/base.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: Test 4 | version: 0.1.0 5 | summary: Test Summary 6 | description: |- 7 | Some test description. 8 | About our test document. 9 | paths: 10 | /anything/selectGlobalServer: 11 | x-my-ignore: true 12 | get: 13 | operationId: selectGlobalServer 14 | responses: 15 | "200": 16 | description: OK 17 | headers: 18 | X-Optional-Header: 19 | schema: 20 | type: string 21 | "404": 22 | description: Not found 23 | content: 24 | application/json: 25 | schema: 26 | $ref: "./components.yaml#/components/schemas/Products" 27 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/components.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | components: 3 | schemas: 4 | Products: 5 | type: array 6 | items: 7 | $ref: '#/components/schemas/Product' 8 | Product: 9 | type: object 10 | required: 11 | - id 12 | - name 13 | - price 14 | - stock 15 | properties: 16 | id: 17 | type: integer 18 | format: int64 19 | name: 20 | type: string 21 | price: 22 | type: number 23 | format: double 24 | tags: 25 | type: array 26 | items: 27 | type: string 28 | stock: 29 | type: integer 30 | # TODO: Figure out why this breaks merge step 31 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.1.0", 3 | "info": { 4 | "title": "Test", 5 | "version": "0.1.0", 6 | "summary": "Test Summary", 7 | "description": "Some test description.\nAbout our test document." 8 | }, 9 | "paths": { 10 | "/anything/selectGlobalServer": { 11 | "x-my-ignore": { 12 | "servers": [ 13 | { 14 | "url": "http://localhost:35123", 15 | "description": "The default server." 16 | } 17 | ] 18 | }, 19 | "get": { 20 | "operationId": "selectGlobalServer", 21 | "responses": { 22 | "200": { 23 | "description": "OK", 24 | "headers": { 25 | "X-Optional-Header": { 26 | "schema": { 27 | "type": "string" 28 | } 29 | } 30 | } 31 | }, 32 | "404": { 33 | "description": "Not found", 34 | "content": { 35 | "application/json": { 36 | "schema": { 37 | "$ref": "./components.yaml#/components/schemas/Products" 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | "servers": [ 44 | { 45 | "url": "http://localhost:35123", 46 | "description": "The default server." 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /pkg/overlay/testdata/expected.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: Test 4 | version: 0.1.0 5 | summary: Test Summary 6 | description: |- 7 | Some test description. 8 | About our test document. 9 | paths: 10 | /anything/selectGlobalServer: 11 | x-my-ignore: 12 | servers: 13 | - url: http://localhost:35123 14 | description: The default server. 15 | get: 16 | operationId: selectGlobalServer 17 | responses: 18 | "200": 19 | description: OK 20 | headers: 21 | X-Optional-Header: 22 | schema: 23 | type: string 24 | "404": 25 | description: Not found 26 | content: 27 | application/json: 28 | schema: 29 | $ref: "./components.yaml#/components/schemas/Products" 30 | servers: 31 | - url: http://localhost:35123 32 | description: The default server. 33 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/expectedWrapped.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.1.0" 2 | info: 3 | title: "Test" 4 | version: "0.1.0" 5 | summary: "Test Summary" 6 | description: "Some test description.\nAbout our test document." 7 | paths: 8 | "/anything/selectGlobalServer": 9 | x-my-ignore: {"servers": [{"url": "http://localhost:35123", "description": "The default server."}]} 10 | get: 11 | operationId: "selectGlobalServer" 12 | responses: 13 | "200": 14 | description: "OK" 15 | headers: 16 | "X-Optional-Header": 17 | schema: 18 | type: "string" 19 | "404": 20 | description: "Not found" 21 | content: 22 | "application/json": 23 | schema: {"$ref": "./components.yaml#/components/schemas/Products"} 24 | servers: 25 | - url: "http://localhost:35123" 26 | description: "The default server." 27 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/overlay-v2.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | x-speakeasy-jsonpath: rfc9535 3 | info: 4 | title: Overlay 5 | version: 0.0.0 6 | actions: 7 | - target: $.paths.*[?(!@.servers)] 8 | update: 9 | servers: 10 | - url: http://localhost:35123 11 | description: The default server. 12 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/overlay.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: Overlay 4 | version: 0.0.0 5 | actions: 6 | - target: $.paths.*.*[?(!@.servers)] 7 | update: 8 | servers: 9 | - url: http://localhost:35123 10 | description: The default server. 11 | -------------------------------------------------------------------------------- /pkg/overlay/testdata/strict-failure.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: Overlay 4 | version: 0.0.0 5 | actions: 6 | - target: $.unknown-element 7 | description: "should make an error" 8 | update: 9 | description: not found 10 | - target: $.info.title 11 | description: "should make a warning" 12 | update: Test 13 | -------------------------------------------------------------------------------- /pkg/transform/convertSwagger.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/getkin/kin-openapi/openapi2" 11 | "github.com/getkin/kin-openapi/openapi2conv" 12 | "github.com/invopop/yaml" 13 | ) 14 | 15 | func ConvertSwagger(ctx context.Context, schemaPath string, yamlOut bool, w io.Writer) error { 16 | schemaBytes, err := os.ReadFile(schemaPath) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | var swaggerDoc openapi2.T 22 | if strings.Contains(schemaPath, ".json") { 23 | err = json.Unmarshal(schemaBytes, &swaggerDoc) 24 | } else { 25 | err = yaml.Unmarshal(schemaBytes, &swaggerDoc) 26 | } 27 | if err != nil { 28 | return err 29 | } 30 | 31 | openAPIV3Spec, err := openapi2conv.ToV3(&swaggerDoc) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | var outBytes []byte 37 | if yamlOut { 38 | outBytes, err = yaml.Marshal(openAPIV3Spec) 39 | } else { 40 | outBytes, err = json.Marshal(openAPIV3Spec) 41 | } 42 | if err != nil { 43 | return err 44 | } 45 | 46 | _, err = w.Write(outBytes) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/transform/convertSwagger_test.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os" 7 | "testing" 8 | 9 | "github.com/pb33f/libopenapi" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestConvertSwaggerYAML(t *testing.T) { 14 | // Create a buffer to store the filtered spec 15 | var buf bytes.Buffer 16 | 17 | // Call ConvertSwagger 18 | err := ConvertSwagger(context.Background(), "../../integration/resources/swagger.yaml", true, &buf) 19 | require.NoError(t, err) 20 | 21 | // Parse the converted spec 22 | filteredDoc, err := libopenapi.NewDocument(buf.Bytes()) 23 | require.NoError(t, err) 24 | 25 | // Validate the OpenAPI v3 model 26 | model, errors := filteredDoc.BuildV3Model() 27 | require.Empty(t, errors) 28 | 29 | // Validate that the model exists 30 | require.NotNil(t, model) 31 | 32 | converted, err := os.ReadFile("../../integration/resources/converted.yaml") 33 | require.NoError(t, err) 34 | 35 | // Normalize and compare as bytes 36 | normalizedConverted := bytes.ReplaceAll(converted, []byte("\r\n"), []byte("\n")) 37 | normalizedBuf := bytes.ReplaceAll(buf.Bytes(), []byte("\r\n"), []byte("\n")) 38 | require.Equal(t, normalizedConverted, normalizedBuf) 39 | } 40 | 41 | func TestConvertSwaggerJSON(t *testing.T) { 42 | // Create a buffer to store the filtered spec 43 | var buf bytes.Buffer 44 | 45 | // Call ConvertSwagger 46 | err := ConvertSwagger(context.Background(), "../../integration/resources/swagger.json", false, &buf) 47 | require.NoError(t, err) 48 | 49 | // Parse the converted spec 50 | filteredDoc, err := libopenapi.NewDocument(buf.Bytes()) 51 | require.NoError(t, err) 52 | 53 | // Validate the OpenAPI v3 model 54 | model, errors := filteredDoc.BuildV3Model() 55 | require.Empty(t, errors) 56 | 57 | // Validate that the model exists 58 | require.NotNil(t, model) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/transform/filterOperations_test.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "testing" 7 | 8 | "github.com/pb33f/libopenapi" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestFilterOperations(t *testing.T) { 14 | // Create a buffer to store the filtered spec 15 | var buf bytes.Buffer 16 | 17 | // Call FilterOperations to remove the delete operation 18 | err := FilterOperations(context.Background(), "../../integration/resources/part1.yaml", []string{"deletePet", "findPetsByStatus"}, false, true, &buf) 19 | require.NoError(t, err) 20 | 21 | // Parse the filtered spec 22 | filteredDoc, err := libopenapi.NewDocument(buf.Bytes()) 23 | require.NoError(t, err) 24 | 25 | model, errors := filteredDoc.BuildV3Model() 26 | require.Empty(t, errors) 27 | 28 | // Check that the delete operation is removed 29 | paths := model.Model.Paths 30 | petPath, ok := paths.PathItems.Get("/pet/{petId}") 31 | require.True(t, ok) 32 | assert.Nil(t, petPath.Delete, "Delete operation should be removed") 33 | 34 | // Check that the findPetsByStatus operation is removed 35 | // The entire path should be removed because findPetsByStatus was the only operation in it 36 | _, ok = paths.PathItems.Get("/pet/findByStatus") 37 | require.False(t, ok) 38 | 39 | // Check that other operations still exist 40 | assert.NotNil(t, petPath.Get, "Get operation should still exist") 41 | 42 | // Check components 43 | components := model.Model.Components 44 | 45 | // Check schemas 46 | pet, ok := components.Schemas.Get("Pet") 47 | assert.True(t, ok, "Used schema 'Pet' should still exist") 48 | assert.NotNil(t, pet) 49 | 50 | // Check for removed schemas 51 | order, ok := components.Schemas.Get("Order") 52 | assert.False(t, ok, "Schema 'Order' should be removed") 53 | assert.Nil(t, order) 54 | 55 | user, ok := components.Schemas.Get("User") 56 | assert.False(t, ok, "Schema 'User' should be removed") 57 | assert.Nil(t, user) 58 | 59 | // Check responses 60 | unauthorized, ok := components.Responses.Get("Unauthorized") 61 | assert.True(t, ok, "Response 'Unauthorized' should still exist") 62 | assert.NotNil(t, unauthorized) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/transform/format_test.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "os" 8 | "testing" 9 | 10 | "github.com/pb33f/libopenapi" 11 | "github.com/stretchr/testify/require" 12 | "gopkg.in/yaml.v3" 13 | ) 14 | 15 | func TestFormat(t *testing.T) { 16 | // Create a buffer to store the formatted spec 17 | var testInput bytes.Buffer 18 | var testOutput bytes.Buffer 19 | 20 | // Call FormatDocument to format the spec 21 | err := FormatDocument(context.Background(), "../../integration/resources/unformatted.yaml", true, &testInput) 22 | require.NoError(t, err) 23 | 24 | // Parse the formatted spec 25 | formattedDoc, err := libopenapi.NewDocument(testInput.Bytes()) 26 | require.NoError(t, err) 27 | 28 | // Check that the formatted spec is valid 29 | _, errors := formattedDoc.BuildV3Model() 30 | require.Empty(t, errors) 31 | 32 | // Open the spec we expect to see to compare 33 | file, err := os.Open("../../integration/resources/formatted.yaml") 34 | require.NoError(t, err) 35 | defer file.Close() 36 | 37 | // Read the expected spec into a buffer 38 | reader := bufio.NewReader(file) 39 | testOutput.ReadFrom(reader) 40 | require.NoError(t, err) 41 | 42 | var actual yaml.Node 43 | var expected yaml.Node 44 | 45 | err = yaml.Unmarshal(testInput.Bytes(), &actual) 46 | require.NoError(t, err) 47 | 48 | err = yaml.Unmarshal(testOutput.Bytes(), &expected) 49 | require.NoError(t, err) 50 | 51 | // Require the pre-formatted spec matches the expected spec 52 | require.Equal(t, expected, actual) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/transform/transformer.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import ( 4 | "context" 5 | "github.com/pb33f/libopenapi" 6 | v3 "github.com/pb33f/libopenapi/datamodel/high/v3" 7 | "github.com/speakeasy-api/speakeasy-core/openapi" 8 | "github.com/speakeasy-api/speakeasy/internal/schemas" 9 | "io" 10 | "os" 11 | ) 12 | 13 | type transformer[Args interface{}] struct { 14 | r io.Reader 15 | schemaPath string 16 | jsonOut bool 17 | transformFn func(ctx context.Context, doc libopenapi.Document, model *libopenapi.DocumentModel[v3.Document], args Args) (libopenapi.Document, *libopenapi.DocumentModel[v3.Document], error) 18 | w io.Writer 19 | args Args 20 | } 21 | 22 | func (t transformer[Args]) Do(ctx context.Context) error { 23 | if t.r == nil { 24 | var err error 25 | t.r, err = os.Open(t.schemaPath) 26 | if err != nil { 27 | return err 28 | } 29 | } 30 | 31 | schemaBytes, err := io.ReadAll(t.r) 32 | if err != nil { 33 | return err 34 | } 35 | doc, model, err := openapi.Load(schemaBytes, t.schemaPath) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | _, model, err = t.transformFn(ctx, *doc, model, t.args) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | bytes, err := schemas.Render(model.Index.GetRootNode(), t.schemaPath, !t.jsonOut) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | _, err = t.w.Write(bytes) 51 | return err 52 | } 53 | 54 | // Note, doc.RenderAndReload() is not sufficient because it does not reload changes to the model 55 | func reload(model *libopenapi.DocumentModel[v3.Document], basePath string) (*libopenapi.Document, *libopenapi.DocumentModel[v3.Document], error) { 56 | updatedBytes, err := model.Model.Render() 57 | if err != nil { 58 | return nil, model, err 59 | } 60 | 61 | doc, model, err := openapi.Load(updatedBytes, basePath) 62 | if err != nil { 63 | return doc, model, err 64 | } 65 | 66 | return doc, model, nil 67 | } 68 | -------------------------------------------------------------------------------- /prompts/statemappings.go: -------------------------------------------------------------------------------- 1 | package prompts 2 | 3 | import ( 4 | "context" 5 | 6 | config "github.com/speakeasy-api/sdk-gen-config" 7 | "github.com/speakeasy-api/sdk-gen-config/workflow" 8 | "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" 9 | ) 10 | 11 | type ( 12 | formFunction func(ctx context.Context, quickstart *Quickstart) (*QuickstartState, error) 13 | QuickstartState int 14 | ) 15 | 16 | type Quickstart struct { 17 | WorkflowFile *workflow.Workflow 18 | LanguageConfigs map[string]*config.Configuration 19 | Defaults Defaults 20 | IsUsingSampleOpenAPISpec bool 21 | IsUsingTemplate bool 22 | SDKName string 23 | } 24 | 25 | type Defaults struct { 26 | SchemaPath *string 27 | TargetType *string 28 | 29 | // The template to use for quickstart. A template is a pre-configured OAS spec retrieved 30 | // from the registry schema store. 31 | // The corresponding CLI flag is --from, e.g: 32 | // speakeasy quickstart --from wandering-octopus-129129 33 | Template *string 34 | 35 | TemplateData *shared.SchemaStoreItem 36 | } 37 | 38 | // Define constants using iota 39 | const ( 40 | Complete QuickstartState = iota 41 | SourceBase 42 | TargetBase 43 | ConfigBase 44 | ) 45 | 46 | // TODO: Add Github Configuration Next 47 | var StateMapping map[QuickstartState]formFunction = map[QuickstartState]formFunction{ 48 | SourceBase: sourceBaseForm, 49 | TargetBase: targetBaseForm, 50 | ConfigBase: configBaseForm, 51 | } 52 | -------------------------------------------------------------------------------- /prompts/terraform_release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Terraform Provider 2 | 3 | # This GitHub action creates a release when a tag that matches the pattern 4 | # "v*" (e.g. v0.1.0) is created. 5 | on: 6 | push: 7 | tags: 8 | - 'v*' 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | goreleaser: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 19 | with: 20 | # Allow goreleaser to access older tag information. 21 | fetch-depth: 0 22 | - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 23 | with: 24 | go-version-file: 'go.mod' 25 | cache: true 26 | - name: Import GPG key 27 | uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0 28 | id: import_gpg 29 | with: 30 | gpg_private_key: ${{ secrets.terraform_gpg_private_key }} 31 | passphrase: ${{ secrets.terraform_gpg_passphrase }} 32 | - name: Run GoReleaser 33 | uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 34 | with: 35 | args: release --clean 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} -------------------------------------------------------------------------------- /prompts/terraform_releaser.yaml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | version: 2 4 | 5 | before: 6 | hooks: 7 | # this is just an example and not a requirement for provider building/publishing 8 | - go mod tidy 9 | builds: 10 | - env: 11 | # goreleaser does not work with CGO, it could also complicate 12 | # usage by users in CI/CD systems like Terraform Cloud where 13 | # they are unable to install libraries. 14 | - CGO_ENABLED=0 15 | mod_timestamp: '{{ .CommitTimestamp }}' 16 | flags: 17 | - -trimpath 18 | ldflags: 19 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 20 | goos: 21 | - freebsd 22 | - windows 23 | - linux 24 | - darwin 25 | goarch: 26 | - amd64 27 | - '386' 28 | - arm 29 | - arm64 30 | ignore: 31 | - goos: darwin 32 | goarch: '386' 33 | binary: '{{ .ProjectName }}_v{{ .Version }}' 34 | archives: 35 | - format: zip 36 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 37 | checksum: 38 | extra_files: 39 | - glob: 'terraform-registry-manifest.json' 40 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 41 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 42 | algorithm: sha256 43 | signs: 44 | - artifacts: checksum 45 | args: 46 | # if you are using this in a GitHub action or some other automated pipeline, you 47 | # need to pass the batch flag to indicate its not interactive. 48 | - "--batch" 49 | - "--local-user" 50 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 51 | - "--output" 52 | - "${signature}" 53 | - "--detach-sign" 54 | - "${artifact}" 55 | release: 56 | extra_files: 57 | - glob: 'terraform-registry-manifest.json' 58 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 59 | # If you want to manually examine the release before its live, uncomment this line: 60 | # draft: true 61 | changelog: 62 | disable: true -------------------------------------------------------------------------------- /registry/tagging.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/operations" 7 | "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" 8 | core "github.com/speakeasy-api/speakeasy-core/auth" 9 | ) 10 | 11 | func AddTags(ctx context.Context, namespaceName, revisionDigest string, tags []string) error { 12 | s, err := core.GetSDKFromContext(ctx) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | _, err = s.Artifacts.PostTags(ctx, operations.PostTagsRequest{ 18 | NamespaceName: namespaceName, 19 | AddTags: &shared.AddTags{ 20 | RevisionDigest: revisionDigest, 21 | Tags: tags, 22 | }, 23 | }) 24 | 25 | return err 26 | } 27 | -------------------------------------------------------------------------------- /registry/utils.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/speakeasy-api/speakeasy/internal/config" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/speakeasy-api/sdk-gen-config/workflow" 11 | core "github.com/speakeasy-api/speakeasy-core/auth" 12 | "github.com/speakeasy-api/speakeasy/internal/download" 13 | "github.com/speakeasy-api/speakeasy/internal/log" 14 | ) 15 | 16 | func ResolveSpeakeasyRegistryBundle(ctx context.Context, d workflow.Document, outPath string) (*download.DownloadedRegistryOpenAPIBundle, error) { 17 | log.From(ctx).Infof("Downloading bundle %s... to %s\n", d.Location, outPath) 18 | 19 | if err := os.MkdirAll(filepath.Dir(outPath), os.ModePerm); err != nil { 20 | return nil, err 21 | } 22 | 23 | workspaceSlug := core.GetWorkspaceSlugFromContext(ctx) 24 | organizationSlug := core.GetOrgSlugFromContext(ctx) 25 | if workspaceSlug == "" || organizationSlug == "" { 26 | return nil, fmt.Errorf("unable to use speakeasy registry reference without authenticating") 27 | } 28 | 29 | registryBreakdown := workflow.ParseSpeakeasyRegistryReference(d.Location.Resolve()) 30 | if registryBreakdown == nil { 31 | return nil, fmt.Errorf("failed to parse speakeasy registry reference %s", d.Location) 32 | } 33 | 34 | keyForWorkspace := config.GetWorkspaceAPIKey(organizationSlug, workspaceSlug) 35 | if keyForWorkspace == "" && organizationSlug != "speakeasy-self" { 36 | if registryBreakdown.OrganizationSlug != organizationSlug { 37 | return nil, fmt.Errorf("organization mismatch: %s != %s", registryBreakdown.OrganizationSlug, organizationSlug) 38 | } 39 | 40 | if registryBreakdown.WorkspaceSlug != workspaceSlug { 41 | return nil, fmt.Errorf("workspace mismatch: %s != %s", registryBreakdown.WorkspaceSlug, workspaceSlug) 42 | } 43 | } 44 | 45 | return download.DownloadRegistryOpenAPIBundle(ctx, *registryBreakdown, outPath) 46 | } 47 | 48 | func IsRegistryEnabled(ctx context.Context) bool { 49 | hasSkipSchemaRegistry, _ := core.HasWorkspaceFeatureFlag(ctx, "skip_schema_registry") 50 | telemetryDisabled := core.IsTelemetryDisabled(ctx) 51 | return !hasSkipSchemaRegistry && !telemetryDisabled 52 | } 53 | -------------------------------------------------------------------------------- /scripts/completions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | ) 9 | 10 | func main() { 11 | // Remove existing completions directory 12 | os.RemoveAll("completions") 13 | 14 | // Create new completions directory 15 | err := os.Mkdir("completions", 0755) 16 | if err != nil { 17 | fmt.Fprintf(os.Stderr, "Error creating completions directory: %v\n", err) 18 | os.Exit(1) 19 | } 20 | 21 | // Generate completions for different shells 22 | shells := []string{"bash", "zsh", "fish"} 23 | for _, shell := range shells { 24 | outputFile := filepath.Join("completions", "speakeasy."+shell) 25 | cmd := exec.Command("go", "run", "main.go", "completion", shell) 26 | output, err := cmd.Output() 27 | if err != nil { 28 | fmt.Fprintf(os.Stderr, "Error generating %s completion: %v\n", shell, err) 29 | os.Exit(1) 30 | } 31 | 32 | err = os.WriteFile(outputFile, output, 0644) 33 | if err != nil { 34 | fmt.Fprintf(os.Stderr, "Error writing to %s: %v\n", outputFile, err) 35 | os.Exit(1) 36 | } 37 | } 38 | } 39 | --------------------------------------------------------------------------------