Software ArchitectureInfrastructure as CodeSDK Modules
Versioning & Publishing
Semantic versioning strategies, changelog automation, and multi-language SDK release management.
Versioning & Publishing
Manage synchronized releases across multiple SDK packages with automated versioning, changelogs, and publishing to package registries.
All SDKs must use the same major version to ensure protocol compatibility. Breaking changes in proto definitions require a major version bump across all SDKs.
Versioning Strategy
Monorepo Configuration
package.json
.changeset/config.json
Changesets Configuration
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": [
"@changesets/changelog-github",
{ "repo": "org/platform" }
],
"commit": false,
"fixed": [
["@org/proto", "@org/sdk-typescript"]
],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"privatePackages": {
"version": true,
"tag": true
},
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}Root Package Configuration
{
"name": "@org/platform",
"private": true,
"workspaces": [
"packages/*",
"services/*",
"apps/*"
],
"scripts": {
"changeset": "changeset",
"changeset:add": "changeset add",
"changeset:status": "changeset status",
"version": "changeset version && pnpm install --lockfile-only",
"version:all": "pnpm run version && pnpm run version:go && pnpm run version:dotnet && pnpm run version:python",
"version:go": "node scripts/version-go.mjs",
"version:dotnet": "node scripts/version-dotnet.mjs",
"version:python": "node scripts/version-python.mjs",
"release": "pnpm run build && changeset publish",
"release:all": "pnpm run release && pnpm run release:go && pnpm run release:dotnet && pnpm run release:python",
"release:go": "node scripts/release-go.mjs",
"release:dotnet": "node scripts/release-dotnet.mjs",
"release:python": "node scripts/release-python.mjs"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.0",
"turbo": "^2.0.0"
},
"packageManager": "pnpm@9.1.0"
}SDK Package Configurations
{
"name": "@org/sdk-typescript",
"version": "1.2.3",
"description": "Type-safe TypeScript SDK for Org services",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./package.json": "./package.json"
},
"files": [
"dist",
"CHANGELOG.md"
],
"scripts": {
"build": "tsup",
"prepublishOnly": "pnpm run build"
},
"repository": {
"type": "git",
"url": "https://github.com/org/platform.git",
"directory": "packages/sdk-typescript"
},
"publishConfig": {
"access": "restricted",
"registry": "https://npm.pkg.github.com"
},
"dependencies": {
"@org/proto": "workspace:^"
}
}module github.com/org/sdk-go
go 1.22
// Version is managed via git tags: sdk-go/v1.2.3
require (
connectrpc.com/connect v1.16.0
google.golang.org/protobuf v1.33.0
)package sdk
// Version is the current SDK version
const Version = "1.2.3"
// ProtocolVersion is the proto contract version
const ProtocolVersion = "1.2.0"<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<!-- Package metadata -->
<PackageId>Org.Sdk</PackageId>
<Version>1.2.3</Version>
<Authors>Org</Authors>
<Company>Org</Company>
<Description>Type-safe .NET SDK for Org services</Description>
<PackageTags>sdk;grpc;api;client</PackageTags>
<PackageProjectUrl>https://github.com/org/platform</PackageProjectUrl>
<RepositoryUrl>https://github.com/org/platform</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<!-- Versioning -->
<AssemblyVersion>1.2.3.0</AssemblyVersion>
<FileVersion>1.2.3.0</FileVersion>
<InformationalVersion>1.2.3</InformationalVersion>
<!-- Source Link -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
<None Include="CHANGELOG.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
name = "org-sdk"
dynamic = ["version"]
description = "Type-safe Python SDK for Org services"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
authors = [
{ name = "Org", email = "sdk@org.com" }
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Typing :: Typed",
]
keywords = ["sdk", "grpc", "api", "client"]
dependencies = [
"grpcio>=1.62.0",
"protobuf>=4.25.0",
"httpx>=0.27.0",
]
[project.urls]
Homepage = "https://github.com/org/platform"
Documentation = "https://docs.org.com/sdk/python"
Repository = "https://github.com/org/platform"
Changelog = "https://github.com/org/platform/blob/main/packages/sdk-python/CHANGELOG.md"
[tool.hatch.version]
source = "vcs"
raw-options = { root = "../.." }
[tool.hatch.build.hooks.vcs]
version-file = "org_sdk/_version.py"
[tool.hatch.build.targets.sdist]
include = [
"/org_sdk",
"/CHANGELOG.md",
]
[tool.hatch.build.targets.wheel]
packages = ["org_sdk"]# Auto-generated by hatch-vcs
__version__ = "1.2.3"Version Synchronization Scripts
#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const ROOT = process.cwd();
const SDK_PATH = join(ROOT, 'packages/sdk-go');
// Read version from TypeScript SDK (source of truth)
const tsPkg = JSON.parse(
readFileSync(join(ROOT, 'packages/sdk-typescript/package.json'), 'utf-8')
);
const version = tsPkg.version;
console.log(`Updating Go SDK to version ${version}`);
// Update version.go
const versionGoPath = join(SDK_PATH, 'version.go');
const versionGo = readFileSync(versionGoPath, 'utf-8');
const updatedVersionGo = versionGo.replace(
/const Version = "[^"]+"/,
`const Version = "${version}"`
);
writeFileSync(versionGoPath, updatedVersionGo);
console.log(`Updated ${versionGoPath}`);
// Update CHANGELOG reference
const changelogPath = join(SDK_PATH, 'CHANGELOG.md');
const tsChangelog = readFileSync(
join(ROOT, 'packages/sdk-typescript/CHANGELOG.md'),
'utf-8'
);
// Copy changelog with Go-specific header
const goChangelog = `# @org/sdk-go\n\n${tsChangelog.split('\n').slice(2).join('\n')}`;
writeFileSync(changelogPath, goChangelog);
console.log(`Updated ${changelogPath}`);#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const ROOT = process.cwd();
const SDK_PATH = join(ROOT, 'packages/sdk-dotnet');
// Read version from TypeScript SDK
const tsPkg = JSON.parse(
readFileSync(join(ROOT, 'packages/sdk-typescript/package.json'), 'utf-8')
);
const version = tsPkg.version;
console.log(`Updating .NET SDK to version ${version}`);
// Update .csproj
const csprojPath = join(SDK_PATH, 'Org.Sdk.csproj');
let csproj = readFileSync(csprojPath, 'utf-8');
// Update Version tag
csproj = csproj.replace(
/<Version>[^<]+<\/Version>/,
`<Version>${version}</Version>`
);
// Update AssemblyVersion
csproj = csproj.replace(
/<AssemblyVersion>[^<]+<\/AssemblyVersion>/,
`<AssemblyVersion>${version}.0</AssemblyVersion>`
);
// Update FileVersion
csproj = csproj.replace(
/<FileVersion>[^<]+<\/FileVersion>/,
`<FileVersion>${version}.0</FileVersion>`
);
// Update InformationalVersion
csproj = csproj.replace(
/<InformationalVersion>[^<]+<\/InformationalVersion>/,
`<InformationalVersion>${version}</InformationalVersion>`
);
writeFileSync(csprojPath, csproj);
console.log(`Updated ${csprojPath}`);#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const ROOT = process.cwd();
const SDK_PATH = join(ROOT, 'packages/sdk-python');
// Read version from TypeScript SDK
const tsPkg = JSON.parse(
readFileSync(join(ROOT, 'packages/sdk-typescript/package.json'), 'utf-8')
);
const version = tsPkg.version;
console.log(`Updating Python SDK to version ${version}`);
// Update _version.py
const versionPyPath = join(SDK_PATH, 'org_sdk/_version.py');
const versionPy = `# Auto-generated - do not edit manually
__version__ = "${version}"
`;
writeFileSync(versionPyPath, versionPy);
console.log(`Updated ${versionPyPath}`);Release Workflow
Create Changeset
When making changes, create a changeset describing the modification:
pnpm changesetThis creates a markdown file in .changeset/:
---
"@org/proto": minor
"@org/sdk-typescript": minor
---
Add order tracking endpoint with real-time status updates
- New `WatchOrder` streaming RPC for status updates
- Added `OrderEvent` message type
- New `tracking` field on Order messageReview Pending Changes
Check what versions will be bumped:
pnpm changeset statusOutput:
🦋 info Packages to be bumped at minor:
🦋 - @org/proto
🦋 - @org/sdk-typescript
🦋 info Dependent packages to be bumped at patch (due to internal dependencies):
🦋 (none)Version Packages
When ready to release, run version command to consume changesets:
pnpm version:allThis will:
- Bump versions in all package.json files
- Update CHANGELOG.md files
- Sync versions to Go, .NET, and Python SDKs
- Remove consumed changeset files
Publish Release
Publish all packages to their respective registries:
pnpm release:allCI/CD Release Pipeline
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
pull-requests: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
registry-url: 'https://npm.pkg.github.com'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pnpm install
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
version: pnpm version:all
publish: pnpm release:all
title: 'chore: release packages'
commit: 'chore: release packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# Publish Go SDK (via git tags)
- name: Publish Go SDK
if: steps.changesets.outputs.published == 'true'
run: |
VERSION=$(node -p "require('./packages/sdk-typescript/package.json').version")
git tag "sdk-go/v${VERSION}"
git push origin "sdk-go/v${VERSION}"
# Publish .NET SDK to NuGet
- name: Publish .NET SDK
if: steps.changesets.outputs.published == 'true'
run: |
cd packages/sdk-dotnet
dotnet pack -c Release
dotnet nuget push **/*.nupkg \
--api-key ${{ secrets.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
# Publish Python SDK to PyPI
- name: Publish Python SDK
if: steps.changesets.outputs.published == 'true'
run: |
cd packages/sdk-python
pip install build twine
python -m build
twine upload dist/* --skip-existing
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_KEY }}Changelog Generation
# @org/sdk-typescript
## 1.2.3
### Patch Changes
- [`a1b2c3d`](https://github.com/org/platform/commit/a1b2c3d) - Fix retry logic for streaming calls
## 1.2.0
### Minor Changes
- [`e4f5g6h`](https://github.com/org/platform/commit/e4f5g6h) - Add order tracking endpoint with real-time status updates
- New `WatchOrder` streaming RPC for status updates
- Added `OrderEvent` message type
- New `tracking` field on Order message
### Patch Changes
- [`i7j8k9l`](https://github.com/org/platform/commit/i7j8k9l) - Updated dependencies
## 1.1.0
### Minor Changes
- [`m0n1o2p`](https://github.com/org/platform/commit/m0n1o2p) - Add payment refund support
- New `RefundPayment` RPC method
- Added `RefundRequest` and `RefundResponse` messages
## 1.0.0
### Major Changes
- [`q3r4s5t`](https://github.com/org/platform/commit/q3r4s5t) - Initial release
- Order service client
- Payment service client
- HTTP and gRPC transport support
- Authentication middlewarePre-release Versions
For testing unreleased changes:
# Create a pre-release changeset
pnpm changeset pre enter beta
# Add changes
pnpm changeset
# Version (creates 1.2.4-beta.0)
pnpm version
# Exit pre-release mode when ready
pnpm changeset pre exitBreaking Change Policy
Version Compatibility Matrix
| SDK Version | Proto Version | Node.js | Go | .NET | Python |
|---|---|---|---|---|---|
| 1.2.x | 1.2.x | ≥18 | ≥1.21 | ≥6.0 | ≥3.10 |
| 1.1.x | 1.1.x | ≥18 | ≥1.21 | ≥6.0 | ≥3.10 |
| 1.0.x | 1.0.x | ≥18 | ≥1.20 | ≥6.0 | ≥3.9 |
Best Practices
Version Lock Recommendations:
- Pin major versions in production:
"@org/sdk-typescript": "^1.0.0" - Use exact versions for critical systems:
"@org/sdk-typescript": "1.2.3" - Test with latest in CI before upgrading
Consumer Guidelines
{
"dependencies": {
"@org/sdk-typescript": "^1.2.0"
}
}require github.com/org/sdk-go v1.2.0<PackageReference Include="Org.Sdk" Version="1.2.*" />[project]
dependencies = [
"org-sdk>=1.2.0,<2.0.0",
]Related Documentation
- SDK Architecture - Transport abstraction and contract-first design
- Protocol Definitions - Proto and OpenAPI specifications
- Multi-Language SDKs - Language-specific implementations