S3.4 - Add `core release --target sdk` to generate SDKs as a separate release target with breaking change detection. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.5 KiB
5.5 KiB
SDK Release Integration Design (S3.4)
Summary
Add core release --target sdk to generate SDKs as a separate release target. Runs breaking change detection before generating, uses release version for SDK versioning, outputs locally for manual publishing.
Design Decisions
- Separate target:
--target sdkruns ONLY SDK generation (no binary builds) - Local output: Generates to
sdk/directory, user handles publishing - Diff first: Run breaking change detection before generating
- Match version: SDK version matches release version from git tags
CLI
core release --target sdk # Generate SDKs only
core release --target sdk --version v1.2.3 # Explicit version
core release --target sdk --dry-run # Preview what would generate
core release # Normal release (unchanged)
Config Schema
In .core/release.yaml:
sdk:
spec: openapi.yaml # or auto-detect
languages: [typescript, python, go, php]
output: sdk # output directory
package:
name: myapi-sdk
diff:
enabled: true
fail_on_breaking: false # warn but continue
Flow
core release --target sdk
↓
1. Load release config (.core/release.yaml)
↓
2. Check sdk config exists (error if not configured)
↓
3. Determine version (git tag or --version flag)
↓
4. If diff.enabled:
- Get previous tag
- Run oasdiff against current spec
- If breaking && fail_on_breaking: abort
- If breaking && !fail_on_breaking: warn, continue
↓
5. Generate SDKs for each language
- Pass version to generators
- Output to sdk/{language}/
↓
6. Print summary (languages generated, output paths)
Package Structure
pkg/release/
├── sdk.go # RunSDK() orchestration + diff helper ← NEW
├── release.go # Existing Run() unchanged
└── config.go # Existing SDKConfig unchanged
pkg/sdk/
└── sdk.go # Add SetVersion() method ← MODIFY
cmd/core/cmd/
└── release.go # Add --target flag ← MODIFY
RunSDK Implementation
// pkg/release/sdk.go
// RunSDK executes SDK-only release: diff check + generate.
func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error) {
if cfg.SDK == nil {
return nil, fmt.Errorf("sdk not configured in .core/release.yaml")
}
projectDir := cfg.projectDir
if projectDir == "" {
projectDir = "."
}
// Determine version
version := cfg.version
if version == "" {
var err error
version, err = DetermineVersion(projectDir)
if err != nil {
return nil, fmt.Errorf("failed to determine version: %w", err)
}
}
// Run diff check if enabled
if cfg.SDK.Diff.Enabled {
breaking, err := checkBreakingChanges(projectDir, cfg.SDK)
if err != nil {
// Non-fatal: warn and continue
fmt.Printf("Warning: diff check failed: %v\n", err)
} else if breaking {
if cfg.SDK.Diff.FailOnBreaking {
return nil, fmt.Errorf("breaking API changes detected")
}
fmt.Printf("Warning: breaking API changes detected\n")
}
}
if dryRun {
return &SDKRelease{
Version: version,
Languages: cfg.SDK.Languages,
Output: cfg.SDK.Output,
}, nil
}
// Generate SDKs
sdkCfg := toSDKConfig(cfg.SDK)
s := sdk.New(projectDir, sdkCfg)
s.SetVersion(version)
if err := s.Generate(ctx); err != nil {
return nil, fmt.Errorf("sdk generation failed: %w", err)
}
return &SDKRelease{
Version: version,
Languages: cfg.SDK.Languages,
Output: cfg.SDK.Output,
}, nil
}
// SDKRelease holds the result of an SDK release.
type SDKRelease struct {
Version string
Languages []string
Output string
}
CLI Integration
// cmd/core/cmd/release.go
var target string
releaseCmd.StringFlag("target", "Release target (sdk)", &target)
releaseCmd.Action(func() error {
if target == "sdk" {
return runReleaseSDK(dryRun, version)
}
return runRelease(dryRun, version, draft, prerelease)
})
func runReleaseSDK(dryRun bool, version string) error {
ctx := context.Background()
projectDir, _ := os.Getwd()
cfg, err := release.LoadConfig(projectDir)
if err != nil {
return err
}
if version != "" {
cfg.SetVersion(version)
}
fmt.Printf("%s Generating SDKs\n", releaseHeaderStyle.Render("SDK Release:"))
if dryRun {
fmt.Printf(" %s\n", releaseDimStyle.Render("(dry-run mode)"))
}
result, err := release.RunSDK(ctx, cfg, dryRun)
if err != nil {
fmt.Printf("%s %v\n", releaseErrorStyle.Render("Error:"), err)
return err
}
fmt.Printf("%s SDK generation complete\n", releaseSuccessStyle.Render("Success:"))
fmt.Printf(" Version: %s\n", result.Version)
fmt.Printf(" Languages: %v\n", result.Languages)
fmt.Printf(" Output: %s/\n", result.Output)
return nil
}
Implementation Steps
- Add
SetVersion()method topkg/sdk/sdk.go - Create
pkg/release/sdk.gowithRunSDK()and helpers - Add
--targetflag tocmd/core/cmd/release.go - Add
runReleaseSDK()function to CLI - Add tests for
pkg/release/sdk_test.go - Final verification and TODO update
Dependencies
oasdiffCLI (for breaking change detection)- Existing SDK generators (openapi-generator, etc.)