DistributionCenter is the release control plane for Solsynth Express products. It stores publisher-owned products, channels, release metadata, and artifact references exposed through the marketplace API.
The repository also contains the express CLI for managing marketplace products and releases.
- Go 1.26.5 or newer
- A running DistributionCenter API
- A Sphere access token for publisher operations
- A product upload key for CI artifact upload and restricted publishing workflows
Build the service and CLI:
make build
make build-cliThe CLI binary is written to:
bin/express
Install directly with Go:
go install src.solsynth.dev/solsynth/distribution/cmd/express@latestThis installs express into GOBIN, or GOPATH/bin when GOBIN is not set. Ensure that directory is on your PATH.
From a local checkout:
go install ./cmd/expressShow command help without configuring a server:
bin/express --helpThe default API base URL is https://api.solian.app/dist. Do not include /api in the URL; override the default with --base-url, DISTRIBUTION_API_URL, DISTRIBUTION_BASE_URL, or SOL_SYNTH_EXPRESS_URL.
bin/express apps list
bin/express \
--base-url https://distribution.example.com \
apps listexpress auth loginThe default login uses Solian's OAuth device authorization flow with PKCE. It prints a verification URL and user code; finish sign-in in a browser and express waits for the access token. The requested OAuth scope is *.
The OAuth public client ID must be registered with the identity service. Provide it with --client-id or SOL_SYNTH_EXPRESS_CLIENT_ID:
express auth login \
--base-url https://distribution.example.com \
--client-id YOUR_REGISTERED_CLIENT_IDThe discovery document defaults to https://solian.app/.well-known/openid-configuration. Override it with --oidc-discovery-url or SOL_SYNTH_EXPRESS_OIDC_DISCOVERY_URL.
For scripts, direct token login remains available:
express auth login \
--base-url https://distribution.example.com \
--token "$SPHERE_ACCESS_TOKEN"Stored credentials are kept at:
~/Library/Application Support/dev.solsynth.express/config.json
The file is created with owner-only permissions. Override the path with either option:
express --config ~/.config/express/auth.json auth login
SOL_SYNTH_EXPRESS_CONFIG=~/.config/express/auth.json express auth statusInspect authentication state without printing the token:
express auth statusRemove stored credentials:
express auth logoutCredential precedence is:
--tokenand--base-url- Environment variables
- Stored credentials
Supported environment variables:
| Variable | Purpose |
|---|---|
DISTRIBUTION_API_URL |
DistributionCenter base URL |
DISTRIBUTION_BASE_URL |
Alternate base URL variable |
SOL_SYNTH_EXPRESS_URL |
Solsynth Express base URL |
DISTRIBUTION_TOKEN |
Bearer token or product upload key |
SPHERE_ACCESS_TOKEN |
Sphere bearer token |
SOL_SYNTH_EXPRESS_CLIENT_ID |
Registered OAuth public client ID |
SOL_SYNTH_EXPRESS_OIDC_DISCOVERY_URL |
OpenID Connect discovery URL |
OAUTH_CLIENT_ID |
Alternate OAuth client ID variable |
OIDC_DISCOVERY_URL |
Alternate discovery URL variable |
SOL_SYNTH_EXPRESS_CONFIG |
Credential file path |
The CLI supports the complete publisher release lifecycle:
- Create or update the product.
- Create a custom channel when needed.
- Create a draft release.
- Upload and attach one or more platform artifacts.
- Publish the release.
Example:
express auth login \
--client-id YOUR_REGISTERED_CLIENT_ID
express product create \
--publisher acme \
--body product.json
express channel create \
--product PRODUCT_ID \
--body channel.json
express release create \
--product PRODUCT_ID \
--body release.json
express artifact upload \
--product PRODUCT_ID \
--version 1.4.0 \
--file ./client.tar.gz \
--platform macos \
--architecture arm64
express release publish \
--product PRODUCT_ID \
--id RELEASE_IDartifact upload automatically prepares the upload URL, calculates SHA-256, uploads the bytes, and attaches the artifact. Repeat it for each platform and architecture before publishing.
When connected to a terminal, express uses concise prompts for product, channel, and release creation when --body is omitted. Artifact uploads report preparation, upload, and attachment progress.
Omitting --product on commands that need it (channel, release, artifact, update, metrics, upload-key) opens an interactive filterable product picker. Press / to filter by name, arrow keys to navigate, Enter to select, Esc to cancel. Omitting --version on artifact upload reads the current git tag.
Informational output is colorized automatically in terminals. JSON responses remain plain on stdout for scripting. Disable color with NO_COLOR=1 or force a mode with:
EXPRESS_COLOR=always express apps list
EXPRESS_COLOR=never express apps listThe CLI intentionally uses text-only output without emoji.
All API responses are printed as formatted JSON.
express apps list \
--sort updated_at \
--order desc \
--limit 20 \
--offset 0
express publisher-apps list --publisher acmeMutation bodies are read from a JSON file. Use --body - to read from standard input.
express product get --id PRODUCT_ID
express product create \
--publisher acme \
--body product.json
express product update \
--id PRODUCT_ID \
--body product.json
express product delete --id PRODUCT_IDExample product.json:
{
"slug": "desktop-client",
"name": "Desktop Client",
"names": {
"en-US": "Desktop Client",
"zh-CN": "桌面客户端"
},
"description": "Desktop client",
"descriptions": {
"en-US": "Desktop client"
},
"previews": []
}express channel list --product PRODUCT_ID
express channel create --product PRODUCT_ID --body channel.json
express channel update --product PRODUCT_ID --id CHANNEL_ID --body channel.json
express channel delete --product PRODUCT_ID --id CHANNEL_IDExample channel.json:
{
"name": "preview",
"display_name": "Preview",
"artifact_retention": 1
}Built-in channels are stable, beta, nightly, and rolling. Custom channels must be created before they are assigned to releases.
express release list \
--product PRODUCT_ID \
--channel stable \
--platform macos \
--architecture arm64
express release manage \
--product PRODUCT_ID \
--channel stable
express release create \
--product PRODUCT_ID \
--body release.json
express release update \
--product PRODUCT_ID \
--id RELEASE_ID \
--body release-update.json
express release publish --product PRODUCT_ID --id RELEASE_ID
express release yank --product PRODUCT_ID --id RELEASE_ID
express release delete --product PRODUCT_ID --id RELEASE_IDPublishing requires at least one complete artifact. Draft releases can be deleted; published releases should be yanked instead.
The upload command performs the complete S3 workflow:
- Calculate the lowercase SHA-256 digest.
- Request a presigned upload URL.
- Upload the file with the required metadata headers.
- Attach the object to the release.
--version defaults to the current git tag (leading v stripped) when
omitted. --platform and --architecture default from the build host when
omitted (darwin → macos, amd64 → x86_64, and so on).
express artifact upload \
--product PRODUCT_ID \
--version 1.4.0 \
--file ./client.tar.gz \
--platform macos \
--architecture arm64From a tagged git checkout, the same command simplifies to:
express artifact upload \
--product PRODUCT_ID \
--file ./client.tar.gzUse --channel when the version does not already exist:
express artifact upload \
--product PRODUCT_ID \
--version 1.4.0-beta.1 \
--channel beta \
--file ./client.tar.gz \
--platform macos \
--architecture arm64To attach an external HTTP(S) artifact or an already-uploaded object, provide the API body directly:
express artifact attach \
--product PRODUCT_ID \
--release RELEASE_ID \
--body artifact.jsonExample artifact.json for an external download:
{
"download_url": "https://downloads.example.com/client.tar.gz",
"file_name": "client.tar.gz",
"mime_type": "application/gzip",
"size": 123456,
"hash": "sha256...",
"platform": "macos",
"architecture": "arm64"
}express update check \
--product PRODUCT_ID \
--version 1.3.0 \
--channel stable \
--os macos \
--architecture arm64 \
--installation-id INSTALLATION_UUID \
--os-version 14.5 \
--client-version 2.8.1 \
--locale en-US
express update resolve \
--product PRODUCT_ID \
--version 1.3.0 \
--channel stable \
--os macos \
--architecture arm64
express metrics --product PRODUCT_IDUpload keys are product-scoped. The plaintext key is returned only during creation.
express upload-key create \
--product PRODUCT_ID \
--name "GitHub Actions"
express upload-key list --product PRODUCT_ID
express upload-key revoke --product PRODUCT_ID --id KEY_IDUse the returned key through --token, DISTRIBUTION_TOKEN, or SPHERE_ACCESS_TOKEN for upload workflows.
Run focused CLI tests:
go test ./cmd/distributionctl ./internal/marketplaceRun the complete test and vet checks:
go test ./...
go vet ./...The marketplace API contract is documented in docs/MARKETPLACE_API.md.