diff --git a/.changesets/1793042947-3a9d1266.yaml b/.changesets/1793042947-3a9d1266.yaml new file mode 100644 index 000000000..55c0111fd --- /dev/null +++ b/.changesets/1793042947-3a9d1266.yaml @@ -0,0 +1,10 @@ +id: 1793042947-3a9d1266 +features: + - core +targets: + - go +type: fix +bump: patch +description: accept retractions lists in gen.yaml validation +author: 2ynn +date: "2026-10-26" diff --git a/.changesets/1793042976-c1371213.yaml b/.changesets/1793042976-c1371213.yaml new file mode 100644 index 000000000..9b00eede3 --- /dev/null +++ b/.changesets/1793042976-c1371213.yaml @@ -0,0 +1,10 @@ +id: 1793042976-c1371213 +features: + - core +targets: + - mcp-typescript +type: fix +bump: patch +description: accept every MCP registry server name in mcpName +author: 2ynn +date: "2026-10-26" diff --git a/.changesets/1793045736-0d9318ab.yaml b/.changesets/1793045736-0d9318ab.yaml new file mode 100644 index 000000000..c3e12df2f --- /dev/null +++ b/.changesets/1793045736-0d9318ab.yaml @@ -0,0 +1,10 @@ +id: 1793045736-0d9318ab +features: + - core +targets: + - php +type: fix +bump: patch +description: reject Composer package names with trailing invalid characters +author: 2ynn +date: "2026-10-26" diff --git a/.changesets/1793046178-b45e569c.yaml b/.changesets/1793046178-b45e569c.yaml new file mode 100644 index 000000000..524dd0f4a --- /dev/null +++ b/.changesets/1793046178-b45e569c.yaml @@ -0,0 +1,10 @@ +id: 1793046178-b45e569c +features: + - core +targets: + - javav2 +type: fix +bump: patch +description: write correct Maven POM links when githubURL includes a scheme, www., trailing slash or .git +author: 2ynn +date: "2026-10-26" diff --git a/.changesets/1793046459-276bb2d1.yaml b/.changesets/1793046459-276bb2d1.yaml new file mode 100644 index 000000000..65c9ae0a6 --- /dev/null +++ b/.changesets/1793046459-276bb2d1.yaml @@ -0,0 +1,10 @@ +id: 1793046459-276bb2d1 +features: + - core +targets: + - javav2 +type: fix +bump: patch +description: write the Maven POM scm url with https:// +author: 2ynn +date: "2026-10-26" diff --git a/pkg/generate/java_publishing_links_test.go b/pkg/generate/java_publishing_links_test.go new file mode 100644 index 000000000..d2b72fa11 --- /dev/null +++ b/pkg/generate/java_publishing_links_test.go @@ -0,0 +1,103 @@ +package generate + +import ( + "context" + "os" + "path/filepath" + "testing" + + generationaccess "github.com/speakeasy-api/generation-context/access" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const javaPublishingLinksSpec = `openapi: 3.1.0 +info: + title: Acme + version: 1.0.0 +servers: + - url: https://api.example.com +paths: + /widgets: + get: + operationId: listWidgets + responses: + "200": + description: OK +` + +func TestJavaPublishingLinksNormalizeGithubURL(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + githubURL string + pomURL string + scmURL string + scmConnection string + }{ + "canonical": { + githubURL: "github.com/acme/acme-java", + pomURL: "https://github.com/acme/acme-java", + scmURL: "https://github.com/acme/acme-java", + scmConnection: "scm:git:ssh://git@github.com/acme/acme-java.git", + }, + "scheme, www, .git and trailing slash": { + githubURL: "https://www.github.com/acme/acme-java.git/", + pomURL: "https://github.com/acme/acme-java", + scmURL: "https://github.com/acme/acme-java", + scmConnection: "scm:git:ssh://git@github.com/acme/acme-java.git", + }, + "uppercase scheme, www and .git": { + githubURL: "HTTPS://WWW.github.com/acme/acme-java.GIT", + pomURL: "https://github.com/acme/acme-java", + scmURL: "https://github.com/acme/acme-java", + scmConnection: "scm:git:ssh://git@github.com/acme/acme-java.git", + }, + "non-string value": { + githubURL: "123", + pomURL: "https://123", + scmURL: "https://123", + scmConnection: "scm:git:ssh://git@123.git", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + outDir := t.TempDir() + speakeasyDir := filepath.Join(outDir, ".speakeasy") + require.NoError(t, os.MkdirAll(speakeasyDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(speakeasyDir, "gen.yaml"), []byte(`configVersion: 2.0.0 +generation: + sdkClassName: Acme +java: + version: 0.0.1 + groupID: com.acme + artifactID: acme + githubURL: `+tc.githubURL+` +`), 0o644)) + + g, err := New(WithPublished(true)) + require.NoError(t, err) + + errs := g.Generate( + generationaccess.WithDirect(context.Background(), generationaccess.ElectAGPL()), + []byte(javaPublishingLinksSpec), + "openapi.yaml", + "java", + outDir, + false, + false, + ) + require.Empty(t, errs) + + publishing, err := os.ReadFile(filepath.Join(outDir, "publishing.gradle")) + require.NoError(t, err) + + assert.Contains(t, string(publishing), " url = '"+tc.pomURL+"'\n") + assert.Contains(t, string(publishing), " url = '"+tc.scmURL+"'\n") + assert.Contains(t, string(publishing), " connection = '"+tc.scmConnection+"'\n") + }) + } +} diff --git a/pkg/templates/validation_regex_test.go b/pkg/templates/validation_regex_test.go index 72d665f30..c7593ea09 100644 --- a/pkg/templates/validation_regex_test.go +++ b/pkg/templates/validation_regex_test.go @@ -3,6 +3,7 @@ package templates import ( "fmt" "regexp" + "slices" "strings" "testing" @@ -23,8 +24,29 @@ type validationField struct { Valid []string Invalid []string } - // Declared on some fields in config.ts but read by neither the generator nor the CLI. - ValidationFunc any +} + +// The CLI matches ValidationRegex against the value formatted with %v, so a pattern on a list field rejects every list. +// List fields with a default are already caught by the default value check. +var listFieldsWithoutDefault = map[string][]string{ + "go": {"retractions"}, +} + +func TestListFieldsHaveNoValidationRegex(t *testing.T) { + t.Parallel() + + for template, names := range listFieldsWithoutDefault { + for _, newSDK := range []bool{false, true} { + fields, err := GetLanguageConfigFields(types.NewTargetFromTemplate(template), newSDK) + require.NoError(t, err) + + for _, name := range names { + i := slices.IndexFunc(fields, func(field config.SDKGenConfigField) bool { return field.Name == name }) + require.NotEqual(t, -1, i, "%s field %q not found (newSDK=%v)", template, name, newSDK) + assert.Nil(t, fields[i].ValidationRegex, "%s field %q (newSDK=%v)", template, name, newSDK) + } + } + } } func TestValidationRegexes(t *testing.T) { diff --git a/templates/templates/cli/config.ts b/templates/templates/cli/config.ts index ea810cd55..cbb1a662d 100644 --- a/templates/templates/cli/config.ts +++ b/templates/templates/cli/config.ts @@ -316,6 +316,18 @@ function getConfigFields( "The go module package name. https://go.dev/ref/mod#module-path.", ValidationRegex: /^[\w\d.\-_/]+$/.source, ValidationMessage: "Letters, numbers, or /.-_ only", + ValidationExamples: { + valid: [ + "acme-cli", + "github.com/acme/acme-cli", + "example.com/acme/cli/v2", + ], + invalid: [ + "", + "github.com/acme/acme cli", + "https://github.com/acme/acme-cli", + ], + }, }, cliName: { Name: "cliName", @@ -326,6 +338,10 @@ function getConfigFields( ValidationRegex: /^[a-z][a-z0-9\-]*$/.source, ValidationMessage: "Lowercase letters, numbers, and hyphens only. Must start with a letter.", + ValidationExamples: { + valid: ["acme", "acme-cli", "acme2"], + invalid: ["", "Acme", "acme_cli", "2acme", "-acme"], + }, }, envVarPrefix: { Name: "envVarPrefix", @@ -336,6 +352,10 @@ function getConfigFields( ValidationRegex: /^[A-Z][A-Z0-9_]*$/.source, ValidationMessage: "Uppercase letters, numbers, and underscores only. Must start with a letter.", + ValidationExamples: { + valid: ["ACME", "ACME_CLI", "ACME2"], + invalid: ["", "acme", "ACME-CLI", "_ACME", "2ACME"], + }, }, additionalDependencies: { Name: "additionalDependencies", @@ -482,6 +502,10 @@ function getConfigFields( "Default network timeout applied to each API operation when --timeout is not given (a Go duration such as 60s). Bounds one HTTP operation including retries; operations whose body outlives the call (event streams, JSONL, raw response streams) are exempt so long-lived reads survive, and polling loops re-arm it per request without capping their own total budget. Empty or zero disables the default.", ValidationRegex: /^$|^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$/.source, ValidationMessage: "a Go duration such as 60s or 2m, or empty", + ValidationExamples: { + valid: ["", "60s", "2m", "1h30m", "1.5s", "500ms", "250µs"], + invalid: ["60", "1d", "-5s", "60 s", "s"], + }, }, jqRawOutput: { Name: "jqRawOutput", diff --git a/templates/templates/common/common/config.ts b/templates/templates/common/common/config.ts index 06c94f690..a3cd82660 100644 --- a/templates/templates/common/common/config.ts +++ b/templates/templates/common/common/config.ts @@ -10,6 +10,10 @@ function getCommonConfigFields(newSDK: boolean): SDKGenConfigFields { "The suffix to add to models with writeOnly fields that are created as input models", ValidationRegex: /^[\w\d.\-_]*$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["input", "Input", "request_input", ""], + invalid: ["input v2", "input/v2", "input!"], + }, }, outputModelSuffix: { Name: "outputModelSuffix", @@ -19,6 +23,10 @@ function getCommonConfigFields(newSDK: boolean): SDKGenConfigFields { "The suffix to add to models with writeOnly fields that are created as input models", ValidationRegex: /^[\w\d.\-_]*$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["output", "Output", "request_output", ""], + invalid: ["output v2", "output/v2", "output!"], + }, }, customCasings: { Name: "customCasings", diff --git a/templates/templates/csharp/config.ts b/templates/templates/csharp/config.ts index f020d8750..654440e5c 100644 --- a/templates/templates/csharp/config.ts +++ b/templates/templates/csharp/config.ts @@ -184,6 +184,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, description: { Name: "description", @@ -199,6 +203,10 @@ function getConfigFields( "The NuGet package ID, also used as the root namespace. https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-namespaces.", ValidationRegex: /^[\w\d._]+$/.source, ValidationMessage: "Letters, numbers, or ._ only", + ValidationExamples: { + valid: ["Acme", "Acme.Sdk", "Acme.Petstore.Client", "Acme_Sdk"], + invalid: ["", "Acme-Sdk", "Acme Sdk", "Acme/Sdk"], + }, }, sourceDirectory: { Name: "sourceDirectory", @@ -207,6 +215,10 @@ function getConfigFields( Description: 'The name of the source directory. Default is "src"', ValidationRegex: /^\w*$/.source, ValidationMessage: "Letters, numbers and underscores only", + ValidationExamples: { + valid: ["", "src", "Source", "src_v2"], + invalid: ["src/Acme", "src-v2", "./src"], + }, }, disableNamespacePascalCasingApr2024: { Name: "disableNamespacePascalCasingApr2024", @@ -225,6 +237,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, author: { Name: "author", @@ -408,6 +424,10 @@ function getConfigFields( "The name of the default exception that is thrown when an API error occurs.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, httpClientPrefix: { Name: "httpClientPrefix", @@ -417,6 +437,10 @@ function getConfigFields( "The prefix used to name the HTTP client interface: 'IHttpClient'. If left empty, the SDK name will be used instead.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["", "Acme", "AcmeHttp"], + invalid: ["Acme_", "Acme-Http", "Acme Http"], + }, }, enableCancellationToken: { Name: "enableCancellationToken", diff --git a/templates/templates/go/config.ts b/templates/templates/go/config.ts index a140c7273..d5f9ea19e 100644 --- a/templates/templates/go/config.ts +++ b/templates/templates/go/config.ts @@ -126,6 +126,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, modulePath: { Name: "modulePath", @@ -136,6 +140,21 @@ function getConfigFields( ValidationRegex: /^([\w\d\-~]([\w\d.\-_\/~]*[\w\d\-~])?)?$/.source, ValidationMessage: "Letters, numbers, or /.-_~ only. Cannot start or end with slash or dot. For more information: https://go.dev/ref/mod#go-mod-file-ident", + ValidationExamples: { + valid: [ + "", + "acme", + "github.com/acme/acme-go", + "github.com/acme/acme-go/v2", + ], + invalid: [ + "/github.com/acme/acme-go", + "github.com/acme/acme-go/", + ".acme", + "github.com/acme/acme-go.", + "github.com/acme/acme go", + ], + }, }, packageName: { Name: "packageName", @@ -145,6 +164,21 @@ function getConfigFields( ValidationRegex: /^([\w\d\-~]([\w\d.\-_\/~]*[\w\d\-~])?)?$/.source, ValidationMessage: "Letters, numbers, or /.-_~ only. Cannot start or end with slash or dot. For more information: https://go.dev/ref/mod#go-mod-file-ident", + ValidationExamples: { + valid: [ + "", + "acme", + "github.com/acme/acme-go", + "github.com/acme/acme-go/v2", + ], + invalid: [ + "/github.com/acme/acme-go", + "github.com/acme/acme-go/", + ".acme", + "github.com/acme/acme-go.", + "github.com/acme/acme go", + ], + }, }, sdkPackageAlias: { Name: "sdkPackageAlias", @@ -154,6 +188,10 @@ function getConfigFields( ValidationRegex: /^(\w[\w\d_]*)?$/.source, ValidationMessage: "Letters, numbers, or underscore (_) only. Must start with letter. For more information: https://go.dev/ref/spec#Packages", + ValidationExamples: { + valid: ["", "acme", "acmesdk", "acme_v2"], + invalid: ["acme-sdk", "acme.sdk", "acme/sdk"], + }, }, sdkPackageName: { Name: "sdkPackageName", @@ -164,6 +202,10 @@ function getConfigFields( ValidationRegex: /^(\w[\w\d_]*)?$/.source, ValidationMessage: "Letters, numbers, or underscore (_) only. Must start with letter. For more information: https://go.dev/ref/spec#Packages", + ValidationExamples: { + valid: ["", "acme", "acmesdk", "acme_v2"], + invalid: ["acme-sdk", "acme.sdk", "acme/sdk"], + }, }, maxMethodParams: { Name: "maxMethodParams", @@ -173,6 +215,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, clientServerStatusCodesAsErrors: { Name: "clientServerStatusCodesAsErrors", @@ -269,10 +315,6 @@ function getConfigFields( Required: false, Description: "Specify Go module retractions to include in the generated go.mod. Each retraction should have a 'version' field and optional 'comment' field.", - ValidationRegex: - /^v\d+\.\d+\.\d+(-[a-zA-Z0-9\-_.]+)?(\+[a-zA-Z0-9\-_.]+)?$/.source, - ValidationMessage: - "Each version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1'). Example: [{version: 'v1.0.0', comment: 'Published accidentally'}, {version: 'v1.0.1', comment: 'Contains retractions only'}]", }, includeEmptyObjects: { Name: "includeEmptyObjects", @@ -291,6 +333,10 @@ function getConfigFields( "The name of the default error type used to represent API errors", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, respectRequiredFields: { Name: "respectRequiredFields", diff --git a/templates/templates/javav2/auxiliary/publishing.gradle.stmpl b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl index 1dae86496..d868035cc 100644 --- a/templates/templates/javav2/auxiliary/publishing.gradle.stmpl +++ b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl @@ -10,6 +10,12 @@ if (context.Global.Config.Published) { context.LocalComputed.PublishV2 = true; } } + +context.LocalComputed.GithubURL = String(context.Global.Config.GithubURL ?? "") + .replace(/^https?:\/\//i, "") + .replace(/^www\./i, "") + .replace(/\/+$/, "") + .replace(/\.git$/i, ""); sjs``` {{- "" -}} @@ -74,10 +80,10 @@ afterEvaluate { ] name = config.name description = config.description - url = 'https://{{.Global.Config.GithubURL}}' + url = 'https://{{.LocalComputed.GithubURL}}' scm { - url = '{{.Global.Config.GithubURL}}' - connection = 'scm:git:ssh://git@{{.Global.Config.GithubURL}}.git' + url = 'https://{{.LocalComputed.GithubURL}}' + connection = 'scm:git:ssh://git@{{.LocalComputed.GithubURL}}.git' } licenses { license { diff --git a/templates/templates/javav2/config.ts b/templates/templates/javav2/config.ts index fb37a8766..c807c1486 100644 --- a/templates/templates/javav2/config.ts +++ b/templates/templates/javav2/config.ts @@ -247,6 +247,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, templateVersion: { Name: "templateVersion", @@ -255,6 +259,10 @@ function getConfigFields( Description: "The template version to use", ValidationRegex: /v2/.source, ValidationMessage: "Template version must be v2", + ValidationExamples: { + valid: ["v2"], + invalid: ["", "v1", "2"], + }, }, projectName: { Name: "projectName", @@ -264,6 +272,10 @@ function getConfigFields( "Assigns Gradle rootProject.name, which gives a name to the Gradle build. https://docs.gradle.org/current/userguide/multi_project_builds.html#naming_recommendations", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["openapi", "acme-sdk", "acme.sdk"], + invalid: ["", "acme sdk", "acme/sdk"], + }, }, groupID: { Name: "groupID", @@ -274,6 +286,10 @@ function getConfigFields( ValidationRegex: /^\w[\w\d.\-_]*$/.source, ValidationMessage: "Letters, numbers, or .-_ only. Must start with a letter", + ValidationExamples: { + valid: ["org.openapis", "com.acme", "com.acme-corp.sdk"], + invalid: ["", ".com.acme", "-acme", "com/acme", "com acme"], + }, }, artifactID: { Name: "artifactID", @@ -284,6 +300,10 @@ function getConfigFields( ValidationRegex: /^[\w\d\/._-]+$/.source, ValidationMessage: "Letters, numbers, or .-_/ only, must start with a letter", + ValidationExamples: { + valid: ["openapi", "acme-sdk", "acme.sdk"], + invalid: ["", "acme sdk", " acme"], + }, }, packageName: { Name: "packageName", @@ -293,6 +313,17 @@ function getConfigFields( "The base package name for generated classes. If not present . will be used.", ValidationRegex: "^[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$", ValidationMessage: "Lowercase letters, numbers, or ._ only", + ValidationExamples: { + valid: ["org.openapis.openapi", "com.acme.sdk", "com.acme_corp.sdk2"], + invalid: [ + "", + "com.Acme.sdk", + "com.acme-corp.sdk", + "com.acme.", + "com..acme", + "1com.acme", + ], + }, }, ossrhURL: { Name: "ossrhURL", @@ -311,6 +342,25 @@ function getConfigFields( ValidationRegex: /github\.com\/[a-zA-z\d_-]+?\/.+/.source, ValidationMessage: "Must be a valid github.com URL, including an owner and repo (e.g. github.com/owner/repo)", + ValidationExamples: { + valid: [ + "github.com/owner/repo", + "github.com/acme/acme-java", + "https://github.com/acme/acme-java", + "http://github.com/acme/acme-java", + "HTTPS://WWW.github.com/acme/acme-java.GIT", + "www.github.com/acme/acme-java", + "github.com/acme/acme-java.git", + "github.com/acme_labs/acme-java/", + ], + invalid: [ + "", + "gitlab.com/acme/acme-java", + "github.com/acme", + "github.com/acme/", + "git@github.com:acme/acme-java", + ], + }, }, companyName: { Name: "companyName", @@ -349,6 +399,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, flattenGlobalSecurity: { Name: "flattenGlobalSecurity", @@ -422,6 +476,10 @@ function getConfigFields( "The name of the default exception that is thrown when an API error occurs.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, enableCustomCodeRegions: { Name: "enableCustomCodeRegions", @@ -501,6 +559,10 @@ function getConfigFields( ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must start with a capital letter and contain only letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, multipartArrayFormat: { Name: "multipartArrayFormat", diff --git a/templates/templates/mcp-typescript/config.ts b/templates/templates/mcp-typescript/config.ts index 7e327fd95..a206af6e8 100644 --- a/templates/templates/mcp-typescript/config.ts +++ b/templates/templates/mcp-typescript/config.ts @@ -303,6 +303,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, envVarPrefix: { Name: "envVarPrefix", @@ -332,6 +336,15 @@ function getConfigFields( ValidationRegex: /^https?:\/\/.+$/.source, ValidationMessage: "Must be a valid URL starting with http:// or https://", + ValidationExamples: { + valid: ["https://acme-mcp.acme.workers.dev", "http://localhost:8787"], + invalid: [ + "", + "acme-mcp.acme.workers.dev", + "ftp://acme.dev", + "https://", + ], + }, }, packageName: { Name: "packageName", @@ -341,6 +354,10 @@ function getConfigFields( "The npm package name. https://docs.npmjs.com/package-name-guidelines.", ValidationRegex: /^[\w\d._@\/-]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["acme-sdk", "@acme/sdk", "@acme/sdk.core"], + invalid: ["", "acme sdk", "acme:sdk"], + }, }, version: { Name: "version", @@ -349,6 +366,10 @@ function getConfigFields( Description: "The current version of the MCP Server", ValidationRegex: /^[\w\d._-]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, dxtManifestOverlay: { Name: "dxtManifestOverlay", @@ -377,13 +398,27 @@ function getConfigFields( Name: "mcpName", Required: false, Description: - "The unique identifier for the MCP server in reverse-DNS format (e.g., 'io.github.username/server-name'). " + + "The unique identifier for the MCP server: a namespace and a server name separated by one slash, with the namespace usually in reverse-DNS format (e.g., 'io.github.username/server-name'). " + "This is required for publishing to the Anthropic MCP registry. " + "See: https://github.com/modelcontextprotocol/registry/blob/main/docs/guides/publishing/publish-server.md", - ValidationRegex: /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*\/[a-z][a-z0-9\-]*$/ - .source, + ValidationRegex: /^[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+$/.source, ValidationMessage: - "Must be in reverse-DNS format (e.g., 'io.github.username/server-name')", + "Must be a namespace and a server name separated by one slash (e.g., 'io.github.username/server-name'): letters, numbers, '.' or '-' in the namespace, and also '_' in the server name", + ValidationExamples: { + valid: [ + "io.github.acme/petstore-mcp", + "io.github.acme/2petstore", + "com.example/acme", + "io.github.acme-labs/petstore", + "io.github.Acme/petstore_mcp.v2", + ], + invalid: [ + "", + "io.github.acme", + "acme/petstore/mcp", + "io.github.acme/petstore mcp", + ], + }, }, responseFormat: { Name: "responseFormat", diff --git a/templates/templates/php/config.ts b/templates/templates/php/config.ts index cdadd0d1c..7a4c71237 100644 --- a/templates/templates/php/config.ts +++ b/templates/templates/php/config.ts @@ -134,6 +134,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, packageName: { Name: "packageName", @@ -142,10 +146,30 @@ function getConfigFields( Description: "The name of the composer package. https://getcomposer.org/doc/04-schema.md#name", ValidationRegex: - /^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*/ + /^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$/ .source, ValidationMessage: "Package name must match the pattern given here https://getcomposer.org/doc/04-schema.md#name", + ValidationExamples: { + valid: [ + "openapi/openapi", + "acme/petstore-sdk", + "acme/petstore--sdk", + "acme/petstore.sdk", + "acme2/petstore_sdk", + ], + invalid: [ + "", + "acme", + "Acme/petstore", + "acme/", + "/petstore", + "-acme/petstore", + "acme/petstoreSDK", + "acme/petstore sdk", + "acme/petstore---sdk", + ], + }, }, namespace: { Name: "namespace", @@ -158,6 +182,10 @@ function getConfigFields( .source, ValidationMessage: "Each part of namespace must match the pattern given here https://www.php.net/manual/en/language.variables.basics.php", + ValidationExamples: { + valid: ["OpenAPI\\OpenAPI", "Acme\\Sdk", "Acme", "Acme_2\\Sdk"], + invalid: ["", "Acme\\", "\\Acme", "Acme-Sdk", "Acme\\2Sdk", "Acme/Sdk"], + }, }, maxMethodParams: { Name: "maxMethodParams", @@ -167,6 +195,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, flattenGlobalSecurity: { Name: "flattenGlobalSecurity", @@ -231,6 +263,10 @@ function getConfigFields( "The name of the default exception that is thrown when an API error occurs.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, additionalDependencies: { Name: "additionalDependencies", diff --git a/templates/templates/postman/config.ts b/templates/templates/postman/config.ts index ee87765f2..a49be8f3f 100644 --- a/templates/templates/postman/config.ts +++ b/templates/templates/postman/config.ts @@ -22,6 +22,10 @@ function getConfigFields( ValidationRegex: /^[\w\d\-~]([\w\d.\-_\/~]*[\w\d\-~])?$/.source, ValidationMessage: "Letters, numbers, or /.-_~ only. Cannot start or end with slash or dot.", + ValidationExamples: { + valid: ["acme", "acme-api", "acme/api", "a"], + invalid: ["", "acme api", "/acme", ".acme", "acme.", "acme/"], + }, }, fileName: { Name: "fileName", @@ -32,6 +36,10 @@ function getConfigFields( ValidationRegex: /^[\w\d\-~]([\w\d.\-_\/~]*[\w\d\-~])?$/.source, ValidationMessage: "Letters, numbers, or /._~ only. Cannot start or end with slash or dot.", + ValidationExamples: { + valid: ["acme", "acme-api", "acme/api", "a"], + invalid: ["", "acme api", "/acme", ".acme", "acme.", "acme/"], + }, }, imports: { Name: "imports", diff --git a/templates/templates/pythonv2/config.ts b/templates/templates/pythonv2/config.ts index 418cfa04a..f22b8d7ec 100644 --- a/templates/templates/pythonv2/config.ts +++ b/templates/templates/pythonv2/config.ts @@ -149,6 +149,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, packageName: { Name: "packageName", @@ -160,6 +164,10 @@ function getConfigFields( .source, ValidationMessage: "Only ASCII letters and numbers, period, underscore, and hyphen characters allowed. It must start and end with a letter or number.", + ValidationExamples: { + valid: ["openapi", "a", "acme-sdk", "acme.sdk", "acme_sdk2"], + invalid: ["", "-acme", "acme-", "acme sdk", "acme/sdk"], + }, }, moduleName: { Name: "moduleName", @@ -177,6 +185,10 @@ function getConfigFields( ValidationRegex: /^[a-zA-Z0-9_.]*$/.source, ValidationMessage: "Only letters, numbers, underscore (_), or period (.) are allowed", + ValidationExamples: { + valid: ["", "acme", "acme_sdk", "acme.sdk"], + invalid: ["acme-sdk", "acme sdk", "acme/sdk"], + }, }, authors: { Name: "authors", @@ -219,6 +231,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, flatteningOrder: { Name: "flatteningOrder", @@ -306,6 +322,10 @@ function getConfigFields( ValidationRegex: /^[A-Za-z][A-Za-z0-9_]*$/.source, ValidationMessage: "Must start with a letter; remaining characters may be letters, digits, or underscores. PascalCased automatically (e.g. 'param' -> 'Param').", + ValidationExamples: { + valid: ["TypedDict", "Param", "Input_2"], + invalid: ["", "2Param", "_Param", "Typed-Dict"], + }, }, rawResponseHelpers: { Name: "rawResponseHelpers", @@ -380,6 +400,10 @@ function getConfigFields( ValidationRegex: /^v\d+$/.source, ValidationMessage: "Template version must be in the format v1, v2, and so on", + ValidationExamples: { + valid: ["v2", "v10"], + invalid: ["", "2", "V2", "v2.1"], + }, }, flattenRequests: { Name: "flattenRequests", @@ -469,6 +493,10 @@ function getConfigFields( "The name of the default exception that is raised when an API error occurs.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, baseErrorName: { Name: "baseErrorName", @@ -480,6 +508,10 @@ function getConfigFields( ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must start with a capital letter and contain only letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, enableCustomCodeRegions: { Name: "enableCustomCodeRegions", @@ -504,6 +536,10 @@ function getConfigFields( "When value is greater than 0, installs pytest-timeout and sets the global pytest-timeout configuration value, which is the number of seconds before individual tests are timed out.", ValidationRegex: /^\d+$/.source, ValidationMessage: "Integer number of seconds", + ValidationExamples: { + valid: ["0", "30"], + invalid: ["", "-1", "1.5", "30s"], + }, }, packageManager: { Name: "packageManager", diff --git a/templates/templates/ruby/config.ts b/templates/templates/ruby/config.ts index 39311d66b..60ddd0c78 100644 --- a/templates/templates/ruby/config.ts +++ b/templates/templates/ruby/config.ts @@ -204,6 +204,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, packageName: { Name: "packageName", @@ -213,6 +217,10 @@ function getConfigFields( "The distribution name of the Ruby Package. https://guides.rubygems.org/name-your-gem/ Use a lowercase name: a name that differs from its snake_case form only by case gets no gem-name entry file, so Bundler will not autoload it on case-sensitive filesystems.", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["openapi", "acme-sdk", "acme_sdk"], + invalid: ["", "acme sdk", "acme/sdk"], + }, }, author: { Name: "author", @@ -271,6 +279,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/using-speakeasy/create-client-sdks/customize-sdks/parameters/", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, flattenGlobalSecurity: { Name: "flattenGlobalSecurity", @@ -315,6 +327,10 @@ function getConfigFields( "The name of the default exception that is thrown when an API error occurs.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, constFieldsAlwaysOptional: { Name: "constFieldsAlwaysOptional", diff --git a/templates/templates/terraform/config.ts b/templates/templates/terraform/config.ts index 5daa001cf..90c9a4192 100644 --- a/templates/templates/terraform/config.ts +++ b/templates/templates/terraform/config.ts @@ -229,6 +229,10 @@ function getConfigFields( Description: "Current version of the Terraform Provider.", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, environmentVariables: { Name: "environmentVariables", @@ -340,6 +344,10 @@ function getConfigFields( "Terraform Provider name. Prefixes all resource names unless providerTypeNameOverride is set. For providers published in the public Terraform Registry, this typically matches the suffix after 'terraform-provider-' in the GitHub Repository name.", ValidationRegex: /^[\w\d-]+$/.source, ValidationMessage: "Letters, numbers, or - only", + ValidationExamples: { + valid: ["terraform", "acme", "acme-cloud"], + invalid: ["", "acme.cloud", "acme cloud", "acme/cloud"], + }, }, providerTypeNameOverride: { Name: "providerTypeNameOverride", @@ -349,6 +357,10 @@ function getConfigFields( "Overrides the provider type name used for resource naming. When set, this value is used as the prefix for all resource, data source, ephemeral resource, and action type names instead of packageName. This is useful for variant providers that need to use the same resource names as another provider (e.g. a beta provider sharing resource names with the main provider).", ValidationRegex: /^[\w\d-]*$/.source, ValidationMessage: "Letters, numbers, or - only", + ValidationExamples: { + valid: ["", "acme", "acme_cloud"], + invalid: ["acme.cloud", "acme cloud"], + }, }, author: { Name: "author", @@ -383,6 +395,10 @@ function getConfigFields( "The name of the default error type used to represent API errors", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, respectRequiredFields: { Name: "respectRequiredFields", diff --git a/templates/templates/typescriptv2/config.ts b/templates/templates/typescriptv2/config.ts index 410ec0bc8..74a6c17d0 100644 --- a/templates/templates/typescriptv2/config.ts +++ b/templates/templates/typescriptv2/config.ts @@ -394,6 +394,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d._-]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, additionalPackageJSON: { Name: "additionalPackageJSON", @@ -409,6 +413,10 @@ function getConfigFields( ValidationRegex: /^v\d+$/.source, ValidationMessage: "Template version must be in the format v1, v2, and so on", + ValidationExamples: { + valid: ["v2", "v10"], + invalid: ["", "2", "V2", "v2.1"], + }, }, packageName: { Name: "packageName", @@ -418,6 +426,10 @@ function getConfigFields( "The npm package name. https://docs.npmjs.com/package-name-guidelines.", ValidationRegex: /^[\w\d._@\/-]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["acme-sdk", "@acme/sdk", "@acme/sdk.core"], + invalid: ["", "acme sdk", "acme:sdk"], + }, }, author: { Name: "author", @@ -434,6 +446,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, flatteningOrder: { Name: "flatteningOrder", @@ -509,6 +525,10 @@ function getConfigFields( "Controls the generated TypeScript server-sent event stream wrapper class name.", ValidationRegex: /^[A-Za-z_$][A-Za-z0-9_$]*$/.source, ValidationMessage: "Must be a valid TypeScript identifier", + ValidationExamples: { + valid: ["EventStream", "$Stream", "_Stream2"], + invalid: ["", "2Stream", "Event-Stream", "Event Stream"], + }, }, enumFormat: { Name: "enumFormat", @@ -576,6 +596,10 @@ function getConfigFields( ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must start with a capital letter and contain only letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, baseErrorName: { Name: "baseErrorName", @@ -587,6 +611,10 @@ function getConfigFields( ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must start with a capital letter and contain only letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, enableReactQuery: { Name: "enableReactQuery", diff --git a/templates/templates/unity/config.ts b/templates/templates/unity/config.ts index bbda14687..c21b8de27 100644 --- a/templates/templates/unity/config.ts +++ b/templates/templates/unity/config.ts @@ -63,6 +63,10 @@ function getConfigFields( Description: "The current version of the SDK", ValidationRegex: /^[\w\d.\-_]+$/.source, ValidationMessage: "Letters, numbers, or .-_ only", + ValidationExamples: { + valid: ["0.0.1", "1.2.3", "1.2.3-beta.1"], + invalid: ["", "1.2.3 beta", "1.2.3/beta"], + }, }, packageName: { Name: "packageName", @@ -72,6 +76,10 @@ function getConfigFields( "The NuGet package ID, also used as the root namespace. https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-namespaces.", ValidationRegex: /^[\w\d._]+$/.source, ValidationMessage: "Letters, numbers, or ._ only", + ValidationExamples: { + valid: ["Acme", "Acme.Sdk", "Acme.Petstore.Client", "Acme_Sdk"], + invalid: ["", "Acme-Sdk", "Acme Sdk", "Acme/Sdk"], + }, }, disableNamespacePascalCasingApr2024: { Name: "disableNamespacePascalCasingApr2024", @@ -90,6 +98,10 @@ function getConfigFields( "The maximum number of parameters a method can have before the resulting SDK endpoint is no longer 'flattened' and an input object is created instead. 0 will use input objects always. https://www.speakeasy.com/docs/customize-sdks/methods", ValidationRegex: /^\d+$/.source, ValidationMessage: "Numbers only", + ValidationExamples: { + valid: ["0", "4", "999"], + invalid: ["", "-1", "4.5", "four"], + }, }, author: { Name: "author", @@ -145,6 +157,10 @@ function getConfigFields( "The name of the default exception that is thrown when an API error occurs.", ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", + ValidationExamples: { + valid: ["APIError", "SDKException", "AcmeError2", ""], + invalid: ["Acme_Error", "Acme-Error", "Acme Error", "Acme.Error"], + }, }, multipartArrayFormat: { Name: "multipartArrayFormat", diff --git a/zSDKs/sdk-javav2/.speakeasy/gen.lock b/zSDKs/sdk-javav2/.speakeasy/gen.lock index 5c6bfeb9a..ee61d0273 100644 --- a/zSDKs/sdk-javav2/.speakeasy/gen.lock +++ b/zSDKs/sdk-javav2/.speakeasy/gen.lock @@ -511,7 +511,7 @@ trackedFiles: gradlew.bat: last_write_checksum: sha1:755607dcbac0ba8adba338ef00aafc1177ceb2b5 publishing.gradle: - last_write_checksum: sha1:c62a7e5882a34d2fa79eb436d99efa74828f214a + last_write_checksum: sha1:51b6c709d3ad4164ccce8fc7eb514b765fbf27b7 settings.gradle: last_write_checksum: sha1:b5b2f4aa4269fea383621740c89e1a847d952da9 spring/openapi-spring-boot-autoconfigure/build.gradle: diff --git a/zSDKs/sdk-javav2/publishing.gradle b/zSDKs/sdk-javav2/publishing.gradle index 93c11532a..0d5dab54e 100644 --- a/zSDKs/sdk-javav2/publishing.gradle +++ b/zSDKs/sdk-javav2/publishing.gradle @@ -24,7 +24,7 @@ afterEvaluate { description = config.description url = 'https://github.com/owner/repo' scm { - url = 'github.com/owner/repo' + url = 'https://github.com/owner/repo' connection = 'scm:git:ssh://git@github.com/owner/repo.git' } licenses {