Spanda

Publishing the official SDKs

How to publish Spanda Rust, Python, and TypeScript SDKs from this repository using GitHub Actions.

Version policy: SDKs use an independent semver line from workspace and desktop — bump with python3 scripts/bump_version.py patch --stream sdk only when SDK client APIs change. See versioning.md.

Packages and registries

SDK Source path Registry package Release tag pattern
Rust crates/spanda-sdk/ spanda-sdk crates-sdk-vX.Y.Z
Python sdk/python/ spanda-sdk sdk-python-vX.Y.Z
TypeScript sdk/typescript/ @davalgi-spanda/sdk npm-sdk-vX.Y.Z

Version numbers live in (bump together via --stream sdk):

Tag suffix must match the SDK version (for example sdk-python-v0.5.1 when SDK version = "0.5.1").

Control Center desktop (GitHub Releases)

App Source path Distribution Release tag pattern
Tauri shell packages/control-center-desktop/ GitHub Release + workflow artifacts desktop-vX.Y.Z

Keep these three files on the same semver before tagging:

./scripts/verify_desktop_release_ready.sh
# Or bump first:
python3 scripts/bump_version.py patch --stream desktop
git tag desktop-v0.6.3 && git push origin desktop-v0.6.3

Automatic desktop release: When a merged PR has a release:* label and changes Control Center paths, Auto release bumps desktop and pushes desktop-v* (same semver component as workspace). See control-center-versioning.md.

Workflow: desktop-release.yml. Optional secrets: TAURI_UPDATER_PUBKEY, TAURI_SIGNING_PRIVATE_KEY, APPLE_SIGNING_IDENTITY, APPLE_NOTARIZE_PROFILE. See desktop-release-runbook.md.

One-time setup: GitHub secrets

Both publish workflows read repository secrets (not committed files).

  1. Open the Spanda repo on GitHub.
  2. Go to Settings → Secrets and variables → Actions.
  3. Add:
Secret name Used by Purpose
CRATES_IO_TOKEN publish-sdk-rust.yml Upload spanda-sdk to crates.io
PYPI_API_TOKEN publish-sdk-python.yml Upload wheels to PyPI
NPM_TOKEN publish-sdk-typescript.yml Upload @davalgi-spanda/sdk to npm

You need admin (or equivalent) access on the repository to create secrets.

Never commit tokens to the repo, paste them in issues/PRs, or store them in .pypirc / .npmrc under version control.


crates.io (CRATES_IO_TOKEN)

Create a crates.io account and token

  1. Register at crates.io (GitHub login works).
  2. Account settings → API TokensNew Token.
  3. Name it (for example spanda-github-actions) and copy the token.

Tokens expire (for example 365 days). Set a calendar reminder to create a new token and update CRATES_IO_TOKEN in GitHub before expiry.

Add to GitHub

Workflow behavior

On tag push crates-sdk-v*:

  1. cargo test -p spanda-sdk (with and without grpc feature)
  2. cargo package -p spanda-sdk
  3. cargo publish -p spanda-sdk

Manual trigger: Actions → Publish Rust SDK → Run workflow (tests/package only; upload on matching tags).

Install after publish:

cargo add spanda-sdk
# optional gRPC client
cargo add spanda-sdk --features grpc

PyPI (PYPI_API_TOKEN)

Create a PyPI account and token

  1. Register at pypi.org if needed.
  2. Log in → Account settings → API tokens.
  3. Add API token:
    • Scope: entire account (first publish) or project spanda-sdk after it exists.
    • Copy the token immediately — PyPI shows it once. It starts with pypi-AgEI....

For PyPI uploads, the username is always __token__; GitHub Actions only needs the token string as the secret value.

Add to GitHub

Workflow behavior

On tag push sdk-python-v*:

  1. Runs pytest sdk/python/tests
  2. Builds a wheel/sdist in sdk/python/dist
  3. Publishes via pypa/gh-action-pypi-publish

Manual trigger: Actions → Publish Python SDK → Run workflow (tests and build always run; upload only on matching tags).


npm (NPM_TOKEN)

Why @davalgi-spanda and not @spanda

The npm organization name spanda is already taken by another account. Scoped packages must live under an org or user you control. This repo publishes TypeScript as:

@davalgi-spanda/sdk

Create the org at npmjs.com/org/create if needed. Public packages under an org are free — the org itself does not expire.

Create an npm access token

  1. Log in at npmjs.com.
  2. Access Tokensnpmjs.com/settings/~/tokens.
  3. Generate New Token → Granular Access Token:
    • Permissions: Read and write
    • Packages/scopes: @davalgi-spanda/* (or the specific package)
    • Expiration: up to 90 days (npm maximum for write tokens)
    • Bypass 2FA: enable — required for non-interactive publish from GitHub Actions

Copy the token (npm_...).

90-day token rotation

The organization does not expire — only the access token does. npm caps write-enabled granular tokens at 90 days.

Before expiry:

  1. Create a new granular token with the same permissions.
  2. Update NPM_TOKEN in GitHub Settings → Secrets and variables → Actions.
  3. Revoke the old token on npm.

Set a calendar reminder (~80 days) so CI publish does not fail mid-release.

Add to GitHub

Package manifest

sdk/typescript/package.json must include:

{
  "name": "@davalgi-spanda/sdk",
  "publishConfig": { "access": "public" }
}

Workflow behavior

On tag push npm-sdk-v*:

  1. npm ci, npm test, npm run build under sdk/typescript
  2. npm publish --access public

Manual trigger: Actions → Publish TypeScript SDK → Run workflow.


Local dry-run (no publish)

From repo root:

./scripts/verify_sdk_publish_ready.sh

This runs Python tests, builds a PyPI wheel, runs TypeScript tests/build, and checks related packages — without uploading.

Individual checks:

# Python
pip install -e "sdk/python[dev]"
pytest sdk/python/tests -q
(cd sdk/python && python -m build)

# TypeScript
npm ci --prefix sdk/typescript
npm test --prefix sdk/typescript
npm run build --prefix sdk/typescript
npm pack --prefix sdk/typescript

Release a new version

1. Bump versions

Use the SDK stream bump helper (keeps Rust, Python, and TypeScript in sync):

python3 scripts/bump_version.py patch --stream sdk

Or edit manually:

Commit and push to main.

Current release line: 0.5.6 — Recovery Orchestrator client methods (getRecoveryPredictive, listRecoverableEntities, recommendRecovery; Rust gRPC when grpc feature enabled, proto semver from GET /v1/version — currently 1.0.14). Prior 0.5.5 added Twin Cloud history, import-replay, and gRPC twins.

2. Verify locally

./scripts/verify_sdk_publish_ready.sh

3. Tag and push (all three registries)

./scripts/publish_sdk_release.sh          # uses version from crates/spanda-sdk/Cargo.toml
./scripts/publish_sdk_release.sh --dry-run  # preview tags only

Or tag manually:

VERSION=0.5.6
git tag crates-sdk-v${VERSION}
git tag sdk-python-v${VERSION}
git tag npm-sdk-v${VERSION}
git push origin crates-sdk-v${VERSION} sdk-python-v${VERSION} npm-sdk-v${VERSION}

4. Watch CI

GitHub → Actions — confirm Publish Rust SDK, Publish Python SDK, and Publish TypeScript SDK succeed.

5. Verify install

pip install spanda-sdk==0.5.5
python -c "from spanda_sdk import SpandaClient; print(SpandaClient.local())"

cargo add spanda-sdk@0.5.5

npm install @davalgi-spanda/sdk@0.5.5
node -e "import('@davalgi-spanda/sdk').then(m => console.log(m.SpandaClient.local()))"

6. Verify registries (post-publish)

./scripts/verify_sdk_published.sh 0.5.5

GitHub Actions: Verify published SDKs (sdk-publish-verify.yml) — weekly or manual workflow_dispatch.

Check registry pages:

Set a calendar reminder before each token expires and update the GitHub secret.

Registry Secret Typical expiry
crates.io CRATES_IO_TOKEN Up to 365 days (set at token creation)
PyPI PYPI_API_TOKEN Per token policy on pypi.org
npm NPM_TOKEN Up to 90 days (write granular tokens)

Troubleshooting

Symptom Likely cause Fix
crates.io 400 verified email Email not verified on crates.io account crates.io/settings/profile — verify inbox link; re-tag after confirmation
crates.io empty token CRATES_IO_TOKEN missing in GitHub Add secret; re-tag crates-sdk-v*
PyPI publish 403 / invalid credentials Wrong or revoked PYPI_API_TOKEN Regenerate token; update GitHub secret
npm publish 401 / E401 Expired token or missing Bypass 2FA New granular token with Bypass 2FA; update NPM_TOKEN
npm scope / org error Package name not under your org Use @davalgi-spanda/sdk, not @spanda/sdk
npm “organization name not available” Name taken globally Pick another org (for example @davalgi-spanda)
Tag pushed but no upload Tag pattern mismatch Use sdk-python-v* or npm-sdk-v* exactly
Version already exists on registry Re-used tag/version Bump version in manifest; new tag
Workflow green but package missing Publish step skipped Publish runs only on tag push, not workflow_dispatch alone

If a token was exposed (chat, log, commit), revoke it immediately on PyPI/npm and create a new one.


Workflow Tag Package
publish-sdk-rust.yml crates-sdk-v* spanda-sdk
publish-sdk-python.yml sdk-python-v* spanda-sdk (PyPI)
publish-sdk-typescript.yml npm-sdk-v* @davalgi-spanda/sdk
publish-npm-web.yml npm-web-v* @davalgi-spanda/web
desktop-release.yml desktop-v* @spanda/control-center-desktop (GitHub Release)

Legacy Python enterprise client: packages/sdk-python — not published by the canonical SDK workflow.

Web panel (@davalgi-spanda/web)

Same NPM_TOKEN and org as the TypeScript SDK. Package lives in packages/web/ with exports for ControlCenterPanel and index.css.

git tag npm-web-v0.4.0
git push origin npm-web-v0.4.0

See packages/web/PUBLISHING.md.

See also