Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d50505d
feat: add release feature notifications
nieomylnieja May 21, 2026
995679b
Merge remote-tracking branch 'origin/main' into add-new-feature-and-n…
nieomylnieja May 26, 2026
30b2efd
feat: show update command in release notifications
nieomylnieja May 26, 2026
85a3c2f
feat: improve release notifications for version and fix notes
nieomylnieja May 28, 2026
3c39ab3
chore: polish up
nieomylnieja Jun 1, 2026
0e6b507
feat: add interactive update prompts for notifications
nieomylnieja Jun 25, 2026
6e6eed6
feat: add interactive update prompts
nieomylnieja Jun 30, 2026
a050dc6
docs: add update prompt screenshots
nieomylnieja Jul 1, 2026
44c2cd2
docs: remove update prompt screenshots
nieomylnieja Jul 1, 2026
ab0494f
Merge branch 'main' into add-new-feature-and-new-version-notifications
nieomylnieja Jul 2, 2026
4e806ae
fix: skip update prompt form on unsupported Windows shells
nieomylnieja Jul 14, 2026
1335fb3
fix: support update notifications across Windows terminals
nieomylnieja Jul 15, 2026
e738a21
Merge remote-tracking branch 'origin/main' into add-new-feature-and-n…
nieomylnieja Jul 20, 2026
ee3d109
chore: update go.mod
nieomylnieja Jul 20, 2026
45f0896
test: strip ANSI codes in notification output assertions
nieomylnieja Jul 20, 2026
3e66b8d
ci: fix native platform notification jobs
nieomylnieja Jul 21, 2026
f6b3b02
test: fix native notification platform isolation
nieomylnieja Jul 21, 2026
fd92403
test: assert joined Windows console output
nieomylnieja Jul 21, 2026
6951dca
test: set Windows PTY dimensions
nieomylnieja Jul 21, 2026
e17c725
test: run Windows notification test with ConPTY
nieomylnieja Jul 21, 2026
eda1e25
fix: make notification release URL build-time only
nieomylnieja Jul 24, 2026
381e8b2
Merge branch 'main' into add-new-feature-and-new-version-notifications
nieomylnieja Jul 27, 2026
a8c84d3
fix: harden release notification handling
nieomylnieja Jul 27, 2026
fff27ae
feat: improve update notifications and installation detection
nieomylnieja Aug 7, 2026
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
65 changes: 65 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,68 @@ jobs:
cache: false
- name: Run tests
run: make test/unit
notification-platforms:
name: Notification compatibility (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
steps:
- name: Check out code
uses: actions/checkout@v7.0.1
- uses: actions/setup-go@v7.0.0
with:
go-version-file: go.mod
cache: false
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.13"
- name: Set up Bats
id: setup-bats
uses: bats-core/bats-action@4.0.0
with:
bats-version: 1.13.0
- name: Run native platform notification tests
env:
BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
TERM: xterm
run: make test/bats/platform
notification-windows:
name: Notification compatibility (windows-latest)
runs-on: windows-latest
timeout-minutes: 10
steps:
- name: Configure Git line endings
run: git config --global core.autocrlf input
- name: Check out code
uses: actions/checkout@v7.0.1
- uses: actions/setup-go@v7.0.0
with:
go-version-file: go.mod
cache: false
- uses: actions/setup-python@v6.2.0
with:
python-version: "3.13"
- name: Install Windows PTY dependency
run: python -m pip install pywinpty==3.0.5
- name: Set up Bats
id: setup-bats
uses: bats-core/bats-action@4.0.0
with:
bats-version: 1.13.0
- name: Set up MinGW
uses: msys2/setup-msys2@v2
with:
install: make python
msystem: MINGW64
path-type: inherit
update: true
- name: Run native platform notification tests
shell: msys2 {0}
env:
BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
TERM: xterm
run: make test/bats/platform
43 changes: 33 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@ MAKEFLAGS += --silent --no-print-directory
BIN_DIR := ./bin
TEST_DIR := ./test
APP_NAME := sloctl
GO_EXE := $(shell go env GOEXE)
VERSION_PKG := "$(shell go list -m)/internal"
NOTIFICATIONS_PKG := "$(shell go list -m)/internal/notifications"

VERSION ?= 1.0.0-test
BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
REVISION ?= $(shell git rev-parse --short=8 HEAD)
NOTIFICATIONS_RELEASE_URL ?=
NOTIFICATIONS_TEST_RELEASE_PORT ?= 38080
NOTIFICATIONS_TEST_RELEASE_URL := http://127.0.0.1:$(NOTIFICATIONS_TEST_RELEASE_PORT)/repos/nobl9/sloctl/releases/latest

LDFLAGS := -s -w \
-X $(VERSION_PKG).BuildVersion=$(VERSION) \
-X $(VERSION_PKG).BuildGitBranch=$(BRANCH) \
-X $(VERSION_PKG).BuildGitRevision=$(REVISION)

ifneq ($(strip $(NOTIFICATIONS_RELEASE_URL)),)
LDFLAGS += -X $(NOTIFICATIONS_PKG).latestReleaseURL=$(NOTIFICATIONS_RELEASE_URL)
endif

# renovate datasource=github-releases depName=golangci/golangci-lint
GOLANGCI_LINT_VERSION := v2.12.2
# renovate datasource=go depName=golang.org/x/vuln/cmd/govulncheck
Expand Down Expand Up @@ -53,17 +62,18 @@ endef
# ${2} - version
# ${3} - git branch
# ${4} - git revision
# ${5} - notifications release URL
define _build_docker
docker build \
--build-arg LDFLAGS="-X $(VERSION_PKG).BuildVersion=$(2) -X $(VERSION_PKG).BuildGitBranch=$(3) -X $(VERSION_PKG).BuildGitRevision=$(4)" \
--build-arg LDFLAGS="-X $(VERSION_PKG).BuildVersion=$(2) -X $(VERSION_PKG).BuildGitBranch=$(3) -X $(VERSION_PKG).BuildGitRevision=$(4) $(if $(strip $(5)),-X $(NOTIFICATIONS_PKG).latestReleaseURL=$(5))" \
-t "$(1)" .
endef

.PHONY: build
## Build sloctl binary.
build:
$(call _print_step,Building sloctl binary)
go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./cmd/$(APP_NAME)/
go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME)$(GO_EXE) ./cmd/$(APP_NAME)/

.PHONY: install
## Install sloctl binary.
Expand All @@ -75,17 +85,17 @@ install:
## Build sloctl Docker image.
docker:
$(call _print_step,Building sloctl Docker image)
$(call _build_docker,sloctl,$(VERSION),$(BRANCH),$(REVISION))
$(call _build_docker,sloctl,$(VERSION),$(BRANCH),$(REVISION),$(NOTIFICATIONS_RELEASE_URL))

.PHONY: test
## Run all tests.
## Run the standard unit and end-to-end suites (excludes native platform tests).
test: test/unit test/e2e

.PHONY: test/unit test/go/unit test/bats/%
## Run all unit tests.
## Run Go and containerized Bats unit tests (excludes native platform tests).
test/unit: test/go/unit test/bats/unit

.PHONY: test/e2e test/bats/unit test/bats/e2e test/go/e2e-docker
.PHONY: test/e2e test/bats/unit test/bats/platform test/bats/e2e test/go/e2e-docker
## Run all e2e tests.
test/e2e: test/bats/e2e test/go/e2e-docker

Expand All @@ -102,15 +112,28 @@ test/go/e2e-docker:
## Run bats unit tests.
test/bats/unit:
$(call _print_step,Running bats unit tests)
$(call _build_docker,sloctl-unit-test-bin,v1.0.0,PC-123-test,e2602ddc)
$(call _build_docker,sloctl-unit-test-bin,v1.0.0,PC-123-test,e2602ddc,$(NOTIFICATIONS_TEST_RELEASE_URL))
docker build -t sloctl-bats-unit -f $(TEST_DIR)/docker/Dockerfile.unit .
docker run -e TERM=linux --rm \
sloctl-bats-unit -F pretty --filter-tags unit $(TEST_DIR)/*
docker run -e RELEASE_SERVER_PORT=$(NOTIFICATIONS_TEST_RELEASE_PORT) -e TERM=linux --rm \
sloctl-bats-unit -F pretty --filter-tags unit,!platform $(TEST_DIR)/*

## Run native platform notification tests.
test/bats/platform:
$(MAKE) VERSION=v1.0.0 NOTIFICATIONS_RELEASE_URL=$(NOTIFICATIONS_TEST_RELEASE_URL) build
$(call _print_step,Running native platform notification tests)
@set -- --filter-tags platform:unix; \
case "$$(uname -s)" in \
CYGWIN*|MINGW*|MSYS*) set -- --filter-tags platform:windows ;; \
Darwin*) set -- "$$@" --filter-tags platform:macos ;; \
esac; \
RELEASE_SERVER_PORT=$(NOTIFICATIONS_TEST_RELEASE_PORT) bats -F pretty \
--setup-suite-file $(TEST_DIR)/setup_platform_suite.bash \
"$$@" $(TEST_DIR)/notifications.bats

## Run bats e2e tests.
test/bats/e2e:
$(call _print_step,Running bats e2e tests)
$(call _build_docker,sloctl-e2e-test-bin,$(VERSION),$(BRANCH),$(REVISION))
$(call _build_docker,sloctl-e2e-test-bin,$(VERSION),$(BRANCH),$(REVISION),$(NOTIFICATIONS_RELEASE_URL))
docker build -t sloctl-bats-e2e -f $(TEST_DIR)/docker/Dockerfile.e2e .
./scripts/run-e2e-tests.sh sloctl-bats-e2e $(REVISION)

Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,28 @@ brew trust nobl9/sloctl
brew install sloctl
```

### Update notifications

In an interactive terminal,
sloctl checks for a newer GitHub release before running the requested command
and caches completed checks for 24 hours.
It does not check in CI, for development builds,
when standard input or standard error is not a terminal,
or when `SLOCTL_NO_NOTIFICATIONS` is set.

On supported terminals,
Homebrew and `go install` installations offer to run
their corresponding update command.
Choosing **Update** exits after the update
instead of running the originally requested sloctl command.
Other installation methods show the [installation options](#install)
and continue without an interactive prompt.

Choosing **Skip until next version** stores the release tag
in the operating system's user cache.
If the preference cannot be saved,
sloctl reports the error and may show the notification again.

### Docker

Sloctl official images are hosted on [hub.docker.com](https://hub.docker.com/r/nobl9/sloctl).
Expand Down
3 changes: 3 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@
"dynatrace",
"endef",
"gobin",
"goexe",
"gofmt",
"goimports",
"gojq",
"golangci",
"gosec",
"govulncheck",
"ldflags",
"msys",
"msystem",
"nobl",
"openslo",
"openslosdk",
Expand Down
47 changes: 39 additions & 8 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ Each test file ends with `.bats` suffix.
In addition to helper test utilities which are part of the framework we also
provide custom helpers which are located in `test/test_helper` directory.

Bats tests are currently divided into 2 categories, end-to-end and unit tests.
The categorization is done through Bats tags. In order to categorize a whole
file as a unit test, add this comment: `# bats file_tags=unit` anywhere in the
file, preferably just below shebang.
Bats tests are primarily divided into two categories: end-to-end and unit tests.
The categorization is done through Bats tags.
Platform compatibility tags are orthogonal to those categories and select tests
for the native `make test/bats/platform` target.
To categorize a whole file as a unit test, add
`# bats file_tags=unit` anywhere in the file, preferably just below the shebang.

The end-to-end tests are only run automatically for releases, be it official
version or pre-release (release candidate).
Expand All @@ -78,8 +80,39 @@ SLOCTL_OKTA_AUTH_SERVER=<dev_auth_server> \ # Runs against dev Okta.
make test/e2e
```

Bats tests are fully containerized, refer to Makefile for more details on
how they're executed.
Bats unit and end-to-end tests run in containers.
Platform compatibility tests run natively with `make test/bats/platform`.
Refer to the Makefile for the exact commands.

### Bats output assertions

Prefer exact stdout and stderr assertions for complete CLI messages.
Store input fixtures, such as request payloads and release bodies, under
[test/inputs](../test/inputs/), and store expected output fixtures under
[test/outputs](../test/outputs/).
When a test file needs a narrower fixture root, set `TEST_INPUTS` or
`TEST_OUTPUTS` in `setup_file` and compare against files from there.

Use file-backed assertions for expected output:

```bash
assert_output - < "$TEST_OUTPUTS/result.stdout"
assert_stderr - < "$TEST_OUTPUTS/error.stderr"
```

Use `--partial` only as a last resort when exact output would be unstable for
reasons unrelated to the behavior under test, such as nondeterministic fields
that cannot be normalized.
If `--partial` is necessary, keep the assertion narrow and leave nearby context
explaining why a full output fixture would be brittle.

Interactive terminal tests should prefer deterministic plain-text fixtures.
Set `NO_COLOR=1` and use accessible form mode when the command supports it.
Keep source data, such as release bodies, in [test/inputs](../test/inputs/),
and compare complete stdout or stderr messages against files in
[test/outputs](../test/outputs/).
For notification tests, use the local release fixture server instead of
proxying GitHub, and use `refute_stderr` when stderr must be empty.

### End-to-end tests

Expand All @@ -88,8 +121,6 @@ and use [test helper utility functions](../test/test_helper/load.bash).
The helper functions are documented inline in that file; read them before
adding a new test, especially if you need fixture generation or output
assertion helpers.
Prefer asserting entire outputs with predefined _INPUTS_ and _OUTPUTS_ read
from files and NOT redirected in the test's code via _heredoc_.

Input fixtures for e2e tests live under [test/inputs](../test/inputs/).
The fixture directory name must match the test filename without the `.bats`
Expand Down
41 changes: 28 additions & 13 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ require (
charm.land/lipgloss/v2 v2.0.5
github.com/BurntSushi/toml v1.6.0
github.com/OpenSLO/go-sdk v0.9.2
github.com/charmbracelet/glamour v1.0.0
github.com/charmbracelet/x/ansi v0.11.7
github.com/go-playground/validator/v10 v10.30.3
github.com/goccy/go-yaml v1.19.2
github.com/itchyny/gojq v0.12.19
github.com/mattn/go-isatty v0.0.23
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/nobl9/go-yaml v1.0.1
github.com/nobl9/nobl9-go v0.133.1
Expand All @@ -20,46 +23,57 @@ require (
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/tidwall/sjson v1.2.5
golang.org/x/mod v0.38.0
golang.org/x/sync v0.22.0
golang.org/x/term v0.45.0
)

require (
charm.land/bubbles/v2 v2.1.0 // indirect
charm.land/bubbletea/v2 v2.0.6 // indirect
charm.land/bubbles/v2 v2.1.1 // indirect
charm.land/bubbletea/v2 v2.0.8 // indirect
github.com/alecthomas/chroma/v2 v2.27.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260511121909-c840852527f3 // indirect
github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260720091822-7cc6674724ac // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20260720091843-3eef36eaaa28 // indirect
github.com/charmbracelet/x/exp/strings v0.1.0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2/v2 v2.5.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.8 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/nobl9/govy v0.26.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
Expand All @@ -71,13 +85,14 @@ require (
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.8.4 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/tools v0.45.0 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/tools v0.48.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading