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
65 changes: 65 additions & 0 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Checks

# Reusable: called by ci.yaml on pushes and pull requests, and by
# release.yaml before goreleaser publishes anything. Defined once here so the
# checks that gate a release are exactly the ones a PR had to pass.
on:
workflow_call:

permissions:
contents: read

jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
# Read from go.mod so CI can never drift from what the module
# requires (it says 1.25.5, so a pinned 1.24 silently made every
# job download a newer toolchain first).
go-version-file: go.mod
- name: golangci-lint
uses: golangci/golangci-lint-action@v8

test:
name: test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# -race needs a C toolchain, which the Windows runner doesn't have
# set up by default.
- os: ubuntu-latest
raceflag: "-race"
- os: windows-latest
raceflag: ""
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: go vet
run: go vet ./...
# Contract tests are behind the `contract` build tag and hit the live
# hub, so they stay out of CI.
- name: go test
run: go test ${{ matrix.raceflag }} ./...

modules:
name: modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Check mod files
run: |
go mod tidy
git diff --exit-code go.mod go.sum
- name: Verify module hashes
run: go mod verify
22 changes: 22 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: CI

# A PR from a branch in this repo only fires the pull_request event (its push
# doesn't match main), so this doesn't double up. Tag pushes don't match
# either - those go to release.yaml.
on:
pull_request:
push:
branches:
- main

permissions:
contents: read

# A new push to a PR makes the in-flight run pointless.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
uses: ./.github/workflows/checks.yaml
28 changes: 7 additions & 21 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,14 @@ permissions:
contents: write

jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.24
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
# Same lint/test/module checks a pull request has to pass, so a tag can't
# publish something CI would have rejected.
checks:
uses: ./.github/workflows/checks.yaml

goreleaser:
runs-on: ubuntu-latest
needs: lint
needs: checks
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -32,15 +26,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24

- name: Check mod files
run: |
go mod tidy
git diff --exit-code go.mod go.sum

- name: Verify module hashes
run: go mod verify
go-version-file: go.mod

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
Expand All @@ -49,4 +35,4 @@ jobs:
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,28 @@ Some [basic usage](#basic-usage) covered here, for more info, run `stellar --hel
>
> You can force copy mode anywhere (e.g. for testing) by setting `STELLAR_APPLY_MODE=copy`.

### Shell completion

stellar ships tab completion for commands, flags, and theme identifiers
(`author/slug@version`). Candidates come from your local theme cache, so
completion is always instant and works offline. If you want hub themes
suggested too, set `STELLAR_COMPLETION_ONLINE=1` (adds up to ~2s of network
lookup per completion; degrades silently to local-only when offline).

```bash
# bash
stellar completion bash > ~/.local/share/bash-completion/completions/stellar

# zsh
stellar completion zsh > "${fpath[1]}/_stellar"

# fish
stellar completion fish > ~/.config/fish/completions/stellar.fish

# powershell (add to $PROFILE)
stellar completion powershell | Out-String | Invoke-Expression
```

## Why use

**Before:** Getting good starship configs so far was mostly random, from someones github dotfiles, searching for something entirely else...
Expand Down Expand Up @@ -256,7 +278,7 @@ When adding new CLI features, please add corresponding E2E tests in `cmd/e2e_tes
- [ ] **Preview: fix bash formatting**
- [ ] **`stellar preview` on Windows**: `cmd/preview.go` only spawns terminals on macOS/Linux and returns "unsupported platform" on Windows. Needs a Windows Terminal / PowerShell branch that opens a shell with `STARSHIP_CONFIG` set.
- [ ] **Windows packaging**: consider scoop/winget packaging (leftover `stellar.exe.old` from self-update is already cleaned up on the next run).
- [ ] **CI test job**: the release workflow runs no `go test` today; add one (ideally with a `windows-latest` runner) to guard the copy path natively.
- [x] **CI test job**: `go vet` and `go test` (with `-race` on Linux) run on every pull request and again before goreleaser, on both `ubuntu-latest` and `windows-latest`, so a tag can't publish a failing build and the copy path is guarded natively.
- [ ] **`stellar publish` command**: Upload local themes directly to stellar-hub
- Challenge: Need to implement CLI authentication (OAuth flow with browser redirect or API keys)
- Would read from `~/.config/stellar/<author>/<theme>/<version>.toml`
Expand Down
85 changes: 85 additions & 0 deletions cmd/completion_args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"os"
"strings"

"github.com/a3chron/stellar/internal/completion"
"github.com/spf13/cobra"
)

// init wires up shell tab-completion for the commands that take theme
// identifiers ("author/slug@version"). It lives in its own file rather than
// touching apply.go/preview.go/info.go/remove.go directly so those files'
// existing structure stays untouched.
func init() {
applyCmd.ValidArgsFunction = themeIdentifierArgs
previewCmd.ValidArgsFunction = themeIdentifierArgs
infoCmd.ValidArgsFunction = themeIdentifierArgs
removeCmd.ValidArgsFunction = removeIdentifierArgs

// Commands that take no arguments still need this: with no
// ValidArgsFunction cobra returns ShellCompDirectiveDefault and the shell
// falls back to offering filenames, so `stellar list <TAB>` would list the
// user's working directory.
for _, c := range []*cobra.Command{
listCmd, cleanCmd, currentCmd, rollbackCmd, updateCmd, versionCmd,
} {
c.ValidArgsFunction = cobra.NoFileCompletions
}
}

// themeCompletionMode returns the candidate sources for apply/preview/info
// completion. Local-only by default: even a 2s hub round trip makes TAB feel
// broken, and most users complete themes they already have cached. Setting
// STELLAR_COMPLETION_ONLINE=1 opts in to hub suggestions.
//
// Read per invocation (not in init) because every shell completion request
// is its own process and tests toggle the variable at runtime.
func themeCompletionMode() completion.Mode {
if os.Getenv(completion.EnvOnline) == "1" || os.Getenv(completion.EnvOnline) == "true" {
return completion.LocalAndRemote
}
return completion.LocalOnly
}

// themeIdentifierArgs is the ValidArgsFunction for commands that accept
// exactly one identifier (apply, preview, info): once that argument is
// already typed, there's nothing left to complete.
func themeIdentifierArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return completion.ThemeIdentifier(toComplete, themeCompletionMode())
}

// removeIdentifierArgs completes "stellar remove" arguments from the local
// cache only - matching remove's own semantics, it never touches the
// network - and, since remove accepts several identifiers, filters out any
// candidate that's already been typed on the command line so repeated
// completion doesn't keep re-suggesting the same theme.
func removeIdentifierArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
candidates, directive := completion.ThemeIdentifier(toComplete, completion.LocalOnly)
if len(candidates) == 0 || len(args) == 0 {
return candidates, directive
}

already := make(map[string]bool, len(args))
for _, a := range args {
already[a] = true
}

filtered := make([]string, 0, len(candidates))
for _, c := range candidates {
value := c
if idx := strings.IndexByte(c, '\t'); idx != -1 {
value = c[:idx]
}
if already[value] {
continue
}
filtered = append(filtered, c)
}

return filtered, directive
}
Loading
Loading