feat!: rewrite the CLI in Go, with the client generated from the OpenAPI spec - #58
Merged
Merged
Conversation
A proof of concept for rewriting the CLI in Go: oapi-codegen generates the client from openapi/platform-api.json, cobra provides the commands, and config, experiment get/apply/run (by key, file and template) and config profile add/list/select/remove are ported with the TypeScript CLI's messages, flags, exit codes and profile files. Experiment files pass through as order-preserving documents so that output stays byte-compatible with the TypeScript CLI where possible.
…ment dump internal/jsyaml ports the parts of js-yaml's dump the CLI used, and a JSON.stringify-compatible writer, so files written by the Go CLI are byte-identical to the TypeScript CLI's. Checked against a recorded corpus of 527 platform experiments (YAML, JSON and compact JSON) and a fixture of 123 edge cases generated from js-yaml itself. JSON is now read with a JSON decoder: yaml.v3 rejects characters JSON allows unescaped, such as DEL. Requests are paced by the same token bucket as before, honouring the STEADYBIT_RATE_LIMIT_* overrides, and each attempt gets its own deadline once admitted, so waiting for the limiter no longer counts as a timeout. experiment dump is ported; on dev it writes trees identical to the TypeScript CLI's. Flags that took space-separated values in commander (--team A B, -f a b) accept them again.
…vice-profile Every command of the TypeScript CLI now exists in the Go CLI with the same flags, messages and output: console-table-printer's table layout, the problem body appended to errors, ids written back into applied files, read-only fields left out of files written by get. Shell completion is new, from cobra, with examples like every other command. The unchanged container e2e suite passes against the Go image.
internal/platformtest runs commands against an httptest server that records every request. Covers the transport (429s for any method, transport retries only for idempotent ones, the problem body in errors), every command group, the help examples and the SPDX headers. Fixes a crash on a placeholders file listing plain strings.
The CLI is now a single Go binary. Commands, flags, messages, exit codes, profiles, environment variables and file formats are unchanged. - CI tests on Linux, macOS and Windows, checks the generated client against the committed spec, builds against the live spec daily, and runs the unchanged container e2e suite against the Go image. - Releases build the binaries with goreleaser, create the GitHub release, publish npm packages (one per platform, and steadybit, whose launcher starts the right one: npm 12 no longer runs install scripts) and push a multi-arch image of 18 MB instead of 249 MB. - The spec fetcher is now Go and keeps masking the Slack webhook example. - The js-yaml fixture generator stands alone with a pinned js-yaml.
achoimet
marked this pull request as ready for review
September 25, 2026 12:04
It had been left out of the port. A test now pins every command of the TypeScript CLI, so none can go missing again.
This was referenced Sep 25, 2026
Merged
v5.0.0 is the last TypeScript release; its changelog entry is kept as it was released.
# Conflicts: # CONTRIBUTING.md # package-lock.json # package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the TypeScript CLI with a Go CLI (part of Kanbanize #14235). The goal was a rewrite users don't notice: the same commands, flags, messages, exit codes,
~/.steadybitprofiles,STEADYBIT_*variables and install channels, and files written byte for byte as before. What changes is a single static binary, no Node.js runtime, and an API client generated from the platform spec.What's in it
advice validate-statusconfig showconfig profile add|list|ls|select|removeexperiment run|get|apply|delete|dump, including the--templatevariantstemplate list|getexecution get|cancel|property set/add|artifact list/downloadschedule list|get|apply|create|update|enable|disable|deleteservice list|get|apply|delete|risk|experiment list/provide/link/unlink|variable get/setservice-profile list|get|apply|deletesteadybit completion bash|zsh|fish|powershell).api/platform.gen.gois generated withoapi-codegenfromopenapi/platform-api.json(go generate ./api). It needed no workaround for the spec's polymorphism. Files users keep in Git (experiments, schedules, services, profiles) pass through as documents, not generated structs, so fields the spec doesn't know yet are never dropped.internal/jsyaml: a port of the parts of js-yaml's dump the CLI used, plus aJSON.stringify-compatible writer. It covers quoting, folding at 80 columns, JavaScript key order and number formatting, and timestamps as Dates, so YAML and JSON output are byte-identical.internal/platform), behaving as the TypeScript client did:STEADYBIT_RATE_LIMIT_*);-vlogs.--team A B,-f a.yml b.yml) still do. The help layout is kept, with every command showing examples.Distribution and CI
releases/latest/download/steadybit_linux_amd64.tar.gzis a stable URL.npm install -g steadybitkeeps working. It installs@steadybit/cli-<os>-<cpu>as an optional dependency, and a small launcher starts it. npm 12 no longer runs install scripts, so downloading on install isn't an option. The launcher passes Ctrl-C and exit codes through (130 on Ctrl-C, checked with the e2e expect scripts).gofmtand a check that the generated client matches the committed spec;api-compatibility, which builds against the live spec daily and before each release and warns when the generated client would change;Compatibility evidence
JSYAML_CORPUStest). A committed fixture of 123 edge cases generated from js-yaml 5.4.1 covers quoting, folding, Unicode, numbers and key order.experiment dump: for three teams (284 files: 14 experiments and 270 runs), the YAML and JSON trees are identical to the TypeScript CLI's.cli-e2e-go-, all deleted afterwards:.000Z;--delete-experimentsguard;internal/platformtest). The container e2e suite passes 20/20.Behaviour changes, on purpose
-kwithout a value or a missing required flag use cobra's wording (required flag(s) "key" not set) instead of commander's. The exit code is still 1.Release
This ships as v6.0.0. v5.0.0 was released from
mainas the last TypeScript version, so users have a fallback.Needs setup before the first release
@steadybit/cli-*packages must be allowed under the@steadybitscope, with trusted publishing configured for each, as it is forsteadybittoday.snyk/actions/golang.getExperimentExecutions_1/2/3) generate names likeGetExperimentExecutions1, and the Slack webhook example should be checked or rotated.