Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
dist
node_modules
.git
.github
.idea
node_modules
dist
npm/dist
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# The fixtures compare exact bytes and the scripts run in Linux containers, so line
# endings stay LF on every platform.
* text=auto eol=lf
*.zip binary
10 changes: 2 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
version: 2
updates:
- package-ecosystem: npm
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
groups:
dev-dependencies:
dependency-type: development
update-types:
- minor
- patch
production-dependencies:
dependency-type: production
go-dependencies:
update-types:
- minor
- patch
Expand Down
131 changes: 79 additions & 52 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,81 +15,61 @@ on:

jobs:
verify:
runs-on: ubuntu-latest
# Paths, terminals and line endings differ between them, and users run all three.
strategy:
# Each platform's failures are worth seeing, not only the first one's.
fail-fast: false
matrix:
node: [22, 24, 26]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/setup-go@v7
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- run: npm install -g npm@12
- run: npm ci
- run: npm run ci
go-version-file: go.mod
- name: Formatting
if: runner.os != 'Windows'
run: test -z "$(gofmt -l api cmd internal)" || { gofmt -l api cmd internal; exit 1; }
- name: The generated client matches the committed spec
if: runner.os != 'Windows'
run: |
go generate ./api
git diff --exit-code -- api/platform.gen.go
- run: go vet ./...
- run: go test ./...

# The committed spec is what the CLI was built against; this checks it against the live
# platform. Breaking changes that touch a request or response the CLI uses surface as
# type errors and fail the job, which also blocks a release. Changes the CLI does not
# depend on only produce a warning to refresh the committed spec.
# platform. A breaking change to an endpoint the CLI uses fails to compile and fails the
# job, which also blocks a release. Changes the CLI does not depend on only warn.
api-compatibility:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/setup-go@v7
with:
node-version: 24
cache: 'npm'
- run: npm install -g npm@12
- run: npm ci
- run: npm run api:fetch-spec
- run: npm run api:generate
- name: Type check against the live platform API
run: npm run verify:typecheck
- name: Report spec drift
go-version-file: go.mod
- run: go run ./internal/tools/spec fetch
- run: go generate ./api
- name: Build against the live platform API
run: go build ./... && go vet ./...
- name: Report API changes
if: always()
run: |
if git diff --quiet -- openapi src/api/generated; then
echo "The committed platform spec matches the live platform." >> "$GITHUB_STEP_SUMMARY"
# The raw spec is not compared: the platform orders some of its maps differently
# from one response to the next. The generated client is deterministic.
if git diff --quiet -- api/platform.gen.go; then
echo "The live platform API matches the committed client." >> "$GITHUB_STEP_SUMMARY"
else
echo "::warning::The platform API changed. Run \`npm run api:update\` and commit the result."
echo "::warning::The platform API changed. Run \`go run ./internal/tools/spec fetch && go generate ./api\` and commit the result."
{
echo "### The platform API changed"
echo
echo "Run \`npm run api:update\` and commit the result."
echo
echo '```'
git diff --stat -- openapi src/api/generated
git diff --stat -- api/platform.gen.go
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
fi

release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [verify, api-compatibility]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@12
- run: npm ci
- name: 'snyk monitor'
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --prune-repeated-subdependencies
command: monitor
- run: npm publish --provenance --access public

docker-build:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
Expand All @@ -106,12 +86,15 @@ jobs:
VERSION="${GITHUB_REF#refs/tags/v}"
MAJOR="${VERSION%%.*}"
echo "tags=steadybit/cli:latest,steadybit/cli:${MAJOR},steadybit/cli:${VERSION}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "push=true" >> "$GITHUB_OUTPUT"
elif [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
echo "tags=steadybit/cli:main" >> "$GITHUB_OUTPUT"
echo "version=main-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
echo "push=true" >> "$GITHUB_OUTPUT"
else
echo "tags=" >> "$GITHUB_OUTPUT"
echo "version=pr-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
echo "push=false" >> "$GITHUB_OUTPUT"
fi
- id: build
Expand All @@ -121,6 +104,7 @@ jobs:
with:
context: ./
load: true
build-args: VERSION=${{ steps.tags.outputs.version }}
- name: Test container
run: docker run --rm ${{ steps.build.outputs.imageid }} -V
- name: Run container smoke tests
Expand All @@ -141,6 +125,7 @@ jobs:
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.tags.outputs.tags }}
build-args: VERSION=${{ steps.tags.outputs.version }}
- name: 'snyk monitor docker image'
if: startsWith(github.ref, 'refs/tags/v')
uses: snyk/actions/docker@master
Expand All @@ -150,3 +135,45 @@ jobs:
command: monitor
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [verify, api-compatibility, docker-build]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@12
- name: Binaries, archives and the GitHub release
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: npm packages from the same binaries
run: go run ./internal/tools/npmpkg -version "${GITHUB_REF_NAME}" -dist dist -out npm/dist
- name: Publish to npm
# The platform packages first: `steadybit` depends on them.
run: |
for dir in npm/dist/cli-*; do
(cd "$dir" && npm publish --provenance --access public)
done
(cd npm/dist/steadybit && npm publish --provenance --access public)
- name: 'snyk monitor'
uses: snyk/actions/golang@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
command: monitor
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
/dist
/npm/dist

/.steadybit.yml
.idea
Expand Down
44 changes: 44 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2026 Steadybit GmbH

# Builds the release binaries, archives and checksums. The npm packages and the container
# image are made from these binaries by the release workflow, so every channel ships the
# same build.
version: 2
project_name: steadybit

builds:
- id: steadybit
main: ./cmd/steadybit
binary: steadybit
env:
- CGO_ENABLED=0
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
flags: [-trimpath]
ldflags:
- -s -w -X github.com/steadybit/cli/internal/platform.Version={{ .Version }}
mod_timestamp: '{{ .CommitTimestamp }}'

archives:
- id: steadybit
# Without the version, so that releases/latest/download/<name> is a stable URL.
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
formats: [tar.gz]
format_overrides:
- goos: windows
formats: [zip]
files: [LICENSE, README.md, CHANGELOG.md]

checksum:
name_template: checksums.txt

snapshot:
version_template: '{{ incpatch .Version }}-next'

release:
# The changelog is written by hand in CHANGELOG.md.
mode: keep-existing

changelog:
disable: true
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

5 changes: 0 additions & 5 deletions .prettierignore

This file was deleted.

8 changes: 0 additions & 8 deletions .prettierrc

This file was deleted.

14 changes: 0 additions & 14 deletions .trivyignore.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
vulnerabilities:
- id: CVE-2026-14257
statement: Waiting for an updated npm version
expired_at: 2027-01-01
- id: CVE-2026-69192
statement: Waiting for an updated npm version
expired_at: 2027-01-01
- id: CVE-2026-69152
statement: Waiting for an updated npm version
expired_at: 2027-01-01
- id: CVE-2026-73566
paths:
- usr/local/lib/node_modules/npm/node_modules/tar/package.json
statement: waiting on npm upstream fix
expired_at: 2027-02-01
- id: CVE-2026-14456
statement: Waiting for an upstream debian fix
expired_at: 2027-01-01
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## v6.0.0

- **The CLI is now a single binary written in Go.** It runs without Node.js, and is installed
the same ways: `npm install -g steadybit`, which now installs the binary for your platform
and works with any Node.js from 18 on, or the `steadybit/cli` container image, now 18 MB
instead of 249 MB. It can also be downloaded directly from the GitHub releases. Commands,
flags, messages, exit codes, profiles in `~/.steadybit` and the `STEADYBIT_*` variables
are unchanged, and experiment, schedule and service files are written byte for byte as
before.
- Writing a new experiment's key back into a YAML file no longer rewrites the file: the key
is added at the top and comments, anchors and formatting are kept.
- Shell completion: `steadybit completion bash|zsh|fish|powershell`.
- Errors about a missing required flag are worded differently (`required flag(s) "key" not
set`); they still exit with 1.

## v5.0.0

- `experiment apply --template <id>` creates an experiment from an experiment template, or
Expand Down
Loading
Loading