cli/tasks/plans/2026-01-29-sdk-release-design.md

211 lines
5.5 KiB
Markdown
Raw Normal View History

# 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 sdk` runs 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
```bash
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`:
```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
```go
// 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
```go
// 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
1. Add `SetVersion()` method to `pkg/sdk/sdk.go`
2. Create `pkg/release/sdk.go` with `RunSDK()` and helpers
3. Add `--target` flag to `cmd/core/cmd/release.go`
4. Add `runReleaseSDK()` function to CLI
5. Add tests for `pkg/release/sdk_test.go`
6. Final verification and TODO update
## Dependencies
- `oasdiff` CLI (for breaking change detection)
- Existing SDK generators (openapi-generator, etc.)