From f1ef62e0650abc4ef919c9353a5757a4e76e81e3 Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 15:27:38 -0400 Subject: [PATCH 01/13] test: declare validation examples for structured config fields Add ValidationExamples to every config field whose ValidationRegex describes a structured value (names, identifiers, paths, versions, URLs, durations, numbers) across the common fields and all targets. Fields whose pattern only lists fixed values, such as ^(true|false)$, are left without examples. TestValidationRegexes checks each example against its pattern, so a future change that rejects a listed valid value or accepts a listed invalid one fails the test. No pattern changes. --- templates/templates/cli/config.ts | 24 +++++++++ templates/templates/common/common/config.ts | 8 +++ templates/templates/csharp/config.ts | 24 +++++++++ templates/templates/go/config.ts | 50 +++++++++++++++++++ templates/templates/javav2/config.ts | 52 ++++++++++++++++++++ templates/templates/mcp-typescript/config.ts | 30 +++++++++++ templates/templates/php/config.ts | 32 ++++++++++++ templates/templates/postman/config.ts | 8 +++ templates/templates/pythonv2/config.ts | 36 ++++++++++++++ templates/templates/ruby/config.ts | 16 ++++++ templates/templates/terraform/config.ts | 16 ++++++ templates/templates/typescriptv2/config.ts | 28 +++++++++++ templates/templates/unity/config.ts | 16 ++++++ 13 files changed, 340 insertions(+) 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..6b55d4c4d 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: ["", "Speakeasy", "Acme"], + 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..a8c0d7d0d 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", @@ -291,6 +337,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/config.ts b/templates/templates/javav2/config.ts index fb37a8766..202e750cf 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,15 @@ 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"], + invalid: [ + "", + "gitlab.com/acme/acme-java", + "github.com/acme", + "github.com/acme/", + ], + }, }, companyName: { Name: "companyName", @@ -349,6 +389,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 +466,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 +549,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..253593f72 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", @@ -384,6 +405,15 @@ function getConfigFields( .source, ValidationMessage: "Must be in reverse-DNS format (e.g., 'io.github.username/server-name')", + ValidationExamples: { + valid: ["io.github.acme/petstore-mcp", "com.example/acme"], + 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..d073eaf07 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", @@ -146,6 +150,22 @@ function getConfigFields( .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", + "acme2/petstore_sdk", + ], + invalid: [ + "", + "acme", + "Acme/petstore", + "acme/", + "/petstore", + "-acme/petstore", + ], + }, }, namespace: { Name: "namespace", @@ -158,6 +178,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 +191,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 +259,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", From 79b8d66daf83ac453abba937212cc8b4203f7c8d Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 15:29:07 -0400 Subject: [PATCH 02/13] fix(go): accept retractions lists in gen.yaml validation The retractions field is a list of {version, comment} objects, but its ValidationRegex matched a single semantic version. The Speakeasy CLI checks a field's pattern against the value formatted with %v, so any configured list, such as [map[version:v1.0.0]], failed gen.yaml validation and could not be used. Drop the pattern from the field and check each retraction's version in the Go target's validateConfig instead, which receives the list itself. The cli config validation tests' helper becomes newLanguageTestConfig so the Go tests can share it. --- .changesets/1793042947-3a9d1266.yaml | 10 +++ pkg/generate/go_config_validation_test.go | 84 +++++++++++++++++++++++ templates/templates/go/config.ts | 19 +++-- 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 .changesets/1793042947-3a9d1266.yaml create mode 100644 pkg/generate/go_config_validation_test.go 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/pkg/generate/go_config_validation_test.go b/pkg/generate/go_config_validation_test.go new file mode 100644 index 000000000..de1c2a311 --- /dev/null +++ b/pkg/generate/go_config_validation_test.go @@ -0,0 +1,84 @@ +package generate + +import ( + "context" + "testing" + + pkgerrors "github.com/speakeasy-api/openapi-generation/v2/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidateConfigGoRetractions(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + retractions any + errMsg string + }{ + "not an array": { + retractions: "v1.0.0", + errMsg: "retractions must be an array", + }, + "item without version": { + retractions: []any{map[string]any{"comment": "Published accidentally"}}, + errMsg: "retractions[0].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')", + }, + "version without v prefix": { + retractions: []any{ + map[string]any{"version": "v1.0.0"}, + map[string]any{"version": "1.0.1"}, + }, + errMsg: "retractions[1].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')", + }, + "item is a string": { + retractions: []any{"v1.0.0"}, + errMsg: "retractions[0].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')", + }, + } + + g, err := New() + require.NoError(t, err) + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := g.validateConfig(context.Background(), newLanguageTestConfig("go", map[string]any{"retractions": tc.retractions}), "go") + require.Error(t, err) + + var validationErr *pkgerrors.ValidationError + require.ErrorAs(t, err, &validationErr) + assert.Equal(t, "validation error: gen.yaml validation error for go: "+tc.errMsg, err.Error()) + }) + } +} + +func TestValidateConfigGoRetractionsSuccess(t *testing.T) { + t.Parallel() + + testCases := map[string]any{ + "absent": nil, + "empty": []any{}, + "valid": []any{ + map[string]any{"version": "v1.0.0", "comment": "Published accidentally"}, + map[string]any{"version": "v1.2.3-beta.1"}, + map[string]any{"version": "v2.0.0+build.5"}, + }, + } + + g, err := New() + require.NoError(t, err) + + for name, retractions := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + cfg := map[string]any{} + if retractions != nil { + cfg["retractions"] = retractions + } + assert.NoError(t, g.validateConfig(context.Background(), newLanguageTestConfig("go", cfg), "go")) + }) + } +} diff --git a/templates/templates/go/config.ts b/templates/templates/go/config.ts index a8c0d7d0d..ddc6cd98c 100644 --- a/templates/templates/go/config.ts +++ b/templates/templates/go/config.ts @@ -315,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", @@ -545,6 +541,21 @@ function validateConfig(cfg: Record): string { return 'optionalMethodArguments must be "pointers", "shared-options", or "method-options"'; } + const retractions = cfg.retractions; + if (retractions !== undefined && retractions !== null) { + if (!Array.isArray(retractions)) { + return "retractions must be an array"; + } + + const semver = /^v\d+\.\d+\.\d+(-[a-zA-Z0-9\-_.]+)?(\+[a-zA-Z0-9\-_.]+)?$/; + for (let i = 0; i < retractions.length; i++) { + const version = retractions[i]?.version; + if (typeof version !== "string" || !semver.test(version)) { + return `retractions[${i}].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')`; + } + } + } + return ""; } From 768209ae3438a71f15b8e2b2b0d6b142d7ccfa3a Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 15:29:36 -0400 Subject: [PATCH 03/13] fix(mcp): accept every MCP registry server name in mcpName The mcpName pattern only allowed lowercase letters and digits in each namespace segment and lowercase letters, digits and hyphens in the server name. The MCP registry's server.json schema accepts ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$, so valid registry names such as io.github.acme-labs/petstore (a GitHub owner with a hyphen) or io.github.Acme/petstore_mcp.v2 failed gen.yaml validation. Use the registry's pattern. The value is only written to server.json and package.json. --- .changesets/1793042976-c1371213.yaml | 10 ++++++++++ templates/templates/mcp-typescript/config.ts | 10 +++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 .changesets/1793042976-c1371213.yaml 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/templates/templates/mcp-typescript/config.ts b/templates/templates/mcp-typescript/config.ts index 253593f72..64cb91398 100644 --- a/templates/templates/mcp-typescript/config.ts +++ b/templates/templates/mcp-typescript/config.ts @@ -401,12 +401,16 @@ function getConfigFields( "The unique identifier for the MCP server 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')", ValidationExamples: { - valid: ["io.github.acme/petstore-mcp", "com.example/acme"], + valid: [ + "io.github.acme/petstore-mcp", + "com.example/acme", + "io.github.acme-labs/petstore", + "io.github.Acme/petstore_mcp.v2", + ], invalid: [ "", "io.github.acme", From df05af8d95f42ac7484d0d1057c377e5a568288c Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 15:58:53 -0400 Subject: [PATCH 04/13] fix(go): keep retractions accepted wherever the template accepts them The validateConfig check added for retractions runs whenever the generator loads gen.yaml, not only in the CLI. It rejected inputs that templateRetractions renders correctly: version ranges such as [v1.0.0, v1.9.9], items keyed Version, and items without a version, which the template skips. It also accepted build metadata such as v2.0.0+build.5, which Go module versions do not allow. Remove the check and its tests. With the field's ValidationRegex already gone, retractions are no longer pattern-checked by the CLI, and the generator accepts what it accepted before; an invalid version still fails when the go command loads go.mod. --- pkg/generate/go_config_validation_test.go | 84 ----------------------- templates/templates/go/config.ts | 15 ---- 2 files changed, 99 deletions(-) delete mode 100644 pkg/generate/go_config_validation_test.go diff --git a/pkg/generate/go_config_validation_test.go b/pkg/generate/go_config_validation_test.go deleted file mode 100644 index de1c2a311..000000000 --- a/pkg/generate/go_config_validation_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package generate - -import ( - "context" - "testing" - - pkgerrors "github.com/speakeasy-api/openapi-generation/v2/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestValidateConfigGoRetractions(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - retractions any - errMsg string - }{ - "not an array": { - retractions: "v1.0.0", - errMsg: "retractions must be an array", - }, - "item without version": { - retractions: []any{map[string]any{"comment": "Published accidentally"}}, - errMsg: "retractions[0].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')", - }, - "version without v prefix": { - retractions: []any{ - map[string]any{"version": "v1.0.0"}, - map[string]any{"version": "1.0.1"}, - }, - errMsg: "retractions[1].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')", - }, - "item is a string": { - retractions: []any{"v1.0.0"}, - errMsg: "retractions[0].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')", - }, - } - - g, err := New() - require.NoError(t, err) - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := g.validateConfig(context.Background(), newLanguageTestConfig("go", map[string]any{"retractions": tc.retractions}), "go") - require.Error(t, err) - - var validationErr *pkgerrors.ValidationError - require.ErrorAs(t, err, &validationErr) - assert.Equal(t, "validation error: gen.yaml validation error for go: "+tc.errMsg, err.Error()) - }) - } -} - -func TestValidateConfigGoRetractionsSuccess(t *testing.T) { - t.Parallel() - - testCases := map[string]any{ - "absent": nil, - "empty": []any{}, - "valid": []any{ - map[string]any{"version": "v1.0.0", "comment": "Published accidentally"}, - map[string]any{"version": "v1.2.3-beta.1"}, - map[string]any{"version": "v2.0.0+build.5"}, - }, - } - - g, err := New() - require.NoError(t, err) - - for name, retractions := range testCases { - t.Run(name, func(t *testing.T) { - t.Parallel() - - cfg := map[string]any{} - if retractions != nil { - cfg["retractions"] = retractions - } - assert.NoError(t, g.validateConfig(context.Background(), newLanguageTestConfig("go", cfg), "go")) - }) - } -} diff --git a/templates/templates/go/config.ts b/templates/templates/go/config.ts index ddc6cd98c..d5f9ea19e 100644 --- a/templates/templates/go/config.ts +++ b/templates/templates/go/config.ts @@ -541,21 +541,6 @@ function validateConfig(cfg: Record): string { return 'optionalMethodArguments must be "pointers", "shared-options", or "method-options"'; } - const retractions = cfg.retractions; - if (retractions !== undefined && retractions !== null) { - if (!Array.isArray(retractions)) { - return "retractions must be an array"; - } - - const semver = /^v\d+\.\d+\.\d+(-[a-zA-Z0-9\-_.]+)?(\+[a-zA-Z0-9\-_.]+)?$/; - for (let i = 0; i < retractions.length; i++) { - const version = retractions[i]?.version; - if (typeof version !== "string" || !semver.test(version)) { - return `retractions[${i}].version must follow semantic versioning format starting with 'v' (e.g., 'v1.0.0', 'v1.2.3-beta.1')`; - } - } - } - return ""; } From b5f48b072ad6b2e1b8460b6fa7be9761fc745dcc Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 15:58:54 -0400 Subject: [PATCH 05/13] fix(mcp): describe the accepted mcpName format in its message The mcpName message still said "reverse-DNS format" although the pattern now follows the MCP registry and also accepts names such as acme/petstore. State the accepted shape instead. Also replace a non-generic httpClientPrefix example in the C# config. --- templates/templates/csharp/config.ts | 2 +- templates/templates/mcp-typescript/config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/templates/csharp/config.ts b/templates/templates/csharp/config.ts index 6b55d4c4d..654440e5c 100644 --- a/templates/templates/csharp/config.ts +++ b/templates/templates/csharp/config.ts @@ -438,7 +438,7 @@ function getConfigFields( ValidationRegex: /^[a-zA-Z0-9]*$/.source, ValidationMessage: "Must only contain letters and numbers", ValidationExamples: { - valid: ["", "Speakeasy", "Acme"], + valid: ["", "Acme", "AcmeHttp"], invalid: ["Acme_", "Acme-Http", "Acme Http"], }, }, diff --git a/templates/templates/mcp-typescript/config.ts b/templates/templates/mcp-typescript/config.ts index 64cb91398..65fc83289 100644 --- a/templates/templates/mcp-typescript/config.ts +++ b/templates/templates/mcp-typescript/config.ts @@ -403,7 +403,7 @@ function getConfigFields( "See: https://github.com/modelcontextprotocol/registry/blob/main/docs/guides/publishing/publish-server.md", 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", From adfef9e91e6080760fafc3561d53421773d72fcf Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 16:15:37 -0400 Subject: [PATCH 06/13] fix(php): anchor the Composer package name pattern The packageName pattern had no closing $, so any value that started with a valid Composer name passed, such as acme/petstoreSDK or acme/petstore sdk. The value is written unchanged to the name field of the generated composer.json, and Composer rejects such names when the SDK is installed. Use Composer's own name pattern from its JSON schema, which matches the same names and is anchored. --- .changesets/1793045736-0d9318ab.yaml | 10 ++++++++++ templates/templates/php/config.ts | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .changesets/1793045736-0d9318ab.yaml 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/templates/templates/php/config.ts b/templates/templates/php/config.ts index d073eaf07..b384713b0 100644 --- a/templates/templates/php/config.ts +++ b/templates/templates/php/config.ts @@ -146,7 +146,7 @@ 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", @@ -164,6 +164,9 @@ function getConfigFields( "acme/", "/petstore", "-acme/petstore", + "acme/petstoreSDK", + "acme/petstore sdk", + "acme/petstore---sdk", ], }, }, From 8695b71378ecf1d641543e04572fca509994501c Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 16:22:59 -0400 Subject: [PATCH 07/13] fix(java): normalize githubURL before writing Maven POM links The githubURL pattern is unanchored, so values such as https://github.com/acme/acme-java, www.github.com/acme/acme-java or github.com/acme/acme-java.git pass validation. publishing.gradle prepends https:// and appends .git to the value, which turned them into POM links such as https://https://github.com/... and scm:git:ssh://git@github.com/acme/acme-java.git.git. Strip a leading http(s):// and www. and a trailing slash and .git before writing the links. The pattern is unchanged, so every value that passes validation today still passes; the canonical github.com/owner/repo form renders exactly as before. --- .changesets/1793046178-b45e569c.yaml | 10 ++++++++++ .../javav2/auxiliary/publishing.gradle.stmpl | 12 +++++++++--- templates/templates/javav2/config.ts | 11 ++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 .changesets/1793046178-b45e569c.yaml 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/templates/templates/javav2/auxiliary/publishing.gradle.stmpl b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl index 1dae86496..4c8e644df 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 = (context.Global.Config.GithubURL || "") + .replace(/^https?:\/\//, "") + .replace(/^www\./, "") + .replace(/\/+$/, "") + .replace(/\.git$/, ""); 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 = '{{.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 202e750cf..2e9d7b733 100644 --- a/templates/templates/javav2/config.ts +++ b/templates/templates/javav2/config.ts @@ -343,12 +343,21 @@ function getConfigFields( 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"], + valid: [ + "github.com/owner/repo", + "github.com/acme/acme-java", + "https://github.com/acme/acme-java", + "http://github.com/acme/acme-java", + "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", ], }, }, From a02ff48ac04b9653bfa8be4fe84c27bef6f15cfc Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 16:22:59 -0400 Subject: [PATCH 08/13] fix(mcp): describe the accepted mcpName format in its description The field description still said the name is in reverse-DNS format, while the pattern follows the MCP registry and also accepts names such as acme/petstore. Describe the namespace/server-name shape and keep reverse-DNS as the usual namespace form. --- templates/templates/mcp-typescript/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/templates/mcp-typescript/config.ts b/templates/templates/mcp-typescript/config.ts index 65fc83289..07c779e92 100644 --- a/templates/templates/mcp-typescript/config.ts +++ b/templates/templates/mcp-typescript/config.ts @@ -398,7 +398,7 @@ 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-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+$/.source, From 3fb5787de5270994d12ca41336fbd7d38794dfb0 Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 16:30:13 -0400 Subject: [PATCH 09/13] fix(java): normalize githubURL case-insensitively and keep non-strings The githubURL pattern is case-sensitive only for github.com, so values such as HTTPS://WWW.github.com/acme/acme-java.GIT pass validation; strip the scheme, www. and .git regardless of case. A non-string githubURL, such as a YAML number, made the normalization throw and abort generation, where the template previously rendered it as is. Convert the value to a string before normalizing. --- .../templates/javav2/auxiliary/publishing.gradle.stmpl | 8 ++++---- templates/templates/javav2/config.ts | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/templates/templates/javav2/auxiliary/publishing.gradle.stmpl b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl index 4c8e644df..626272b9e 100644 --- a/templates/templates/javav2/auxiliary/publishing.gradle.stmpl +++ b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl @@ -11,11 +11,11 @@ if (context.Global.Config.Published) { } } -context.LocalComputed.GithubURL = (context.Global.Config.GithubURL || "") - .replace(/^https?:\/\//, "") - .replace(/^www\./, "") +context.LocalComputed.GithubURL = String(context.Global.Config.GithubURL ?? "") + .replace(/^https?:\/\//i, "") + .replace(/^www\./i, "") .replace(/\/+$/, "") - .replace(/\.git$/, ""); + .replace(/\.git$/i, ""); sjs``` {{- "" -}} diff --git a/templates/templates/javav2/config.ts b/templates/templates/javav2/config.ts index 2e9d7b733..c807c1486 100644 --- a/templates/templates/javav2/config.ts +++ b/templates/templates/javav2/config.ts @@ -348,6 +348,7 @@ function getConfigFields( "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/", From 198fdcb4ff6cb1537528ec27ff18f3072cf08b98 Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 16:56:06 -0400 Subject: [PATCH 10/13] test(java): check rendered publishing links for githubURL variants Generate a published Java SDK for canonical, scheme/www/.git/trailing slash, uppercase and non-string githubURL values and assert the three links written to publishing.gradle. The githubURL examples only check the unchanged pattern, so they do not cover the normalization. --- pkg/generate/java_publishing_links_test.go | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 pkg/generate/java_publishing_links_test.go diff --git a/pkg/generate/java_publishing_links_test.go b/pkg/generate/java_publishing_links_test.go new file mode 100644 index 000000000..f05b583a9 --- /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: "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: "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: "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: "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") + }) + } +} From df4ad6f49bf1225021bc976ed8771442806e0b21 Mon Sep 17 00:00:00 2001 From: 2ynn Date: Mon, 26 Oct 2026 16:56:06 -0400 Subject: [PATCH 11/13] test: guard list fields against validation patterns and add boundary examples The CLI matches a field's ValidationRegex against its 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; add a test for those without one, starting with Go retractions. Also add valid examples for the two-hyphen Composer package name boundary and an MCP server name that starts with a digit. --- pkg/templates/validation_regex_test.go | 24 ++++++++++++++++++++ templates/templates/mcp-typescript/config.ts | 1 + templates/templates/php/config.ts | 1 + 3 files changed, 26 insertions(+) diff --git a/pkg/templates/validation_regex_test.go b/pkg/templates/validation_regex_test.go index 72d665f30..edd510990 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" @@ -27,6 +28,29 @@ type validationField struct { 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) { t.Parallel() diff --git a/templates/templates/mcp-typescript/config.ts b/templates/templates/mcp-typescript/config.ts index 07c779e92..a206af6e8 100644 --- a/templates/templates/mcp-typescript/config.ts +++ b/templates/templates/mcp-typescript/config.ts @@ -407,6 +407,7 @@ function getConfigFields( 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", diff --git a/templates/templates/php/config.ts b/templates/templates/php/config.ts index b384713b0..7a4c71237 100644 --- a/templates/templates/php/config.ts +++ b/templates/templates/php/config.ts @@ -154,6 +154,7 @@ function getConfigFields( valid: [ "openapi/openapi", "acme/petstore-sdk", + "acme/petstore--sdk", "acme/petstore.sdk", "acme2/petstore_sdk", ], From 9601d9f0e9f4245fdabe8b23e6d5f0967e091c11 Mon Sep 17 00:00:00 2001 From: Qynn Schwaab <41802320+2ynn@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:18:00 -0400 Subject: [PATCH 12/13] fix(java): write the Maven POM scm url with https:// (#156) Stacked on #155, which normalizes `githubURL` before the POM links are written (`LocalComputed.GithubURL`). Review after #155; this PR will be retargeted once #155 merges. ## Why `templates/templates/javav2/auxiliary/publishing.gradle.stmpl` writes three POM links from `githubURL`. The project `url` and the SCM `connection` are complete, but the SCM `url` is the bare value: ```groovy url = 'https://github.com/owner/repo' scm { url = 'github.com/owner/repo' connection = 'scm:git:ssh://git@github.com/owner/repo.git' } ``` The Maven POM reference defines `` as "a publicly browsable repository". Without a scheme it is not an absolute URL, so tools that turn it into a link cannot open it. Publishing is not affected: Maven Central only requires the element to be present. Building on #155 matters here: with the raw value, prefixing `https://` would double the scheme for a `githubURL` such as `https://github.com/acme/acme-java`, whose SCM `url` is currently correct. ## What changed - `publishing.gradle.stmpl`: SCM `url` is now `https://{{.LocalComputed.GithubURL}}`, like the project `url`. - `zSDKs/sdk-javav2`: regenerated with `TARGET=review make build-javav2`; `publishing.gradle` changes only in that line, plus its `gen.lock` checksum. - Changeset: `core` / `javav2` patch. ## Testing - `TestJavaPublishingLinksNormalizeGithubURL` (from #155) now expects `https://` in the SCM `url` for every case and passes. - `TARGET=review make build-javav2`: passes; the diff is the one `scm.url` line and its checksum. - `cmd/generate -l java -p` with `githubURL: https://github.com/acme/acme-java` writes `https://github.com/acme/acme-java` for both URLs and `scm:git:ssh://git@github.com/acme/acme-java.git` for the connection. - Not run: publishing to a Maven repository. ## Public-safety check - [x] This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs. - [x] Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (`.claude/skills/public-repo-communication/SKILL.md`). - [x] Generated fixtures and review SDK changes are public-safe. - [x] I reviewed `git diff --check`. --- .changesets/1793046459-276bb2d1.yaml | 10 ++++++++++ pkg/generate/java_publishing_links_test.go | 8 ++++---- .../templates/javav2/auxiliary/publishing.gradle.stmpl | 2 +- zSDKs/sdk-javav2/.speakeasy/gen.lock | 2 +- zSDKs/sdk-javav2/publishing.gradle | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 .changesets/1793046459-276bb2d1.yaml 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 index f05b583a9..d2b72fa11 100644 --- a/pkg/generate/java_publishing_links_test.go +++ b/pkg/generate/java_publishing_links_test.go @@ -38,25 +38,25 @@ func TestJavaPublishingLinksNormalizeGithubURL(t *testing.T) { "canonical": { githubURL: "github.com/acme/acme-java", pomURL: "https://github.com/acme/acme-java", - scmURL: "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: "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: "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: "123", + scmURL: "https://123", scmConnection: "scm:git:ssh://git@123.git", }, } diff --git a/templates/templates/javav2/auxiliary/publishing.gradle.stmpl b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl index 626272b9e..d868035cc 100644 --- a/templates/templates/javav2/auxiliary/publishing.gradle.stmpl +++ b/templates/templates/javav2/auxiliary/publishing.gradle.stmpl @@ -82,7 +82,7 @@ afterEvaluate { description = config.description url = 'https://{{.LocalComputed.GithubURL}}' scm { - url = '{{.LocalComputed.GithubURL}}' + url = 'https://{{.LocalComputed.GithubURL}}' connection = 'scm:git:ssh://git@{{.LocalComputed.GithubURL}}.git' } licenses { 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 { From 3912c69445137b85a89e7e9bd2682b472756a1cf Mon Sep 17 00:00:00 2001 From: 2ynn Date: Tue, 27 Oct 2026 09:37:17 -0400 Subject: [PATCH 13/13] test: reject ValidationFunc in config field definitions No config.ts declares ValidationFunc any more, so drop it from the fields the strict decoder accepts. --- pkg/templates/validation_regex_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/templates/validation_regex_test.go b/pkg/templates/validation_regex_test.go index edd510990..c7593ea09 100644 --- a/pkg/templates/validation_regex_test.go +++ b/pkg/templates/validation_regex_test.go @@ -24,8 +24,6 @@ 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.