From 922c19cda9ef344e0361e78596f5d0c7db4479ec Mon Sep 17 00:00:00 2001 From: Ryu <114303361+ryuapp@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:02:56 +0900 Subject: [PATCH] feat: support Windows releases --- .github/workflows/go.yml | 25 +++++++++++++++++++++--- .goreleaser-self.yaml | 15 +++++++++++++- .goreleaser.yaml | 15 +++++++++++++- README.md | 9 +++++---- internal/cmd/update.go | 18 ----------------- internal/cmd/update_unix.go | 26 +++++++++++++++++++++++++ internal/cmd/update_windows.go | 13 +++++++++++++ internal/settings/settings.go | 3 +-- internal/settings/settings_perm_test.go | 21 ++++++++++++-------- 9 files changed, 108 insertions(+), 37 deletions(-) create mode 100644 internal/cmd/update_unix.go create mode 100644 internal/cmd/update_windows.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 96abad65..51c8c7e6 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,7 +13,7 @@ permissions: contents: read jobs: - build: + checks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 @@ -32,6 +32,23 @@ jobs: - name: Format run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + + - name: Setup Golang with cache + uses: actions/setup-go@v5.5.0 + with: + go-version-file: go.mod + cache: false + - name: Build run: go build -v ./... @@ -41,9 +58,11 @@ jobs: - name: Install sqlclosecheck # TODO revert to github.com/ryanrolds/sqlclosecheck when https://github.com/ryanrolds/sqlclosecheck/pull/47 is merged run: go install github.com/LeMikaelF/sqlclosecheck@b53ecaccb94623dfa6050e4ada4eeecd3a130829 + env: + GOBIN: ${{ runner.temp }}/sqlclosecheck-bin - name: sqlclosecheck - run: go vet -vettool=${HOME}/go/bin/sqlclosecheck ./... + run: go vet "-vettool=${{ runner.temp }}/sqlclosecheck-bin/sqlclosecheck" ./... - name: Staticcheck uses: dominikh/staticcheck-action@v1.4.1 @@ -56,4 +75,4 @@ jobs: run: go test -v ./... - name: Build Turso binary - run: go build -o turso cmd/turso/main.go + run: go build ./cmd/turso diff --git a/.goreleaser-self.yaml b/.goreleaser-self.yaml index f5c16f59..7e71e625 100644 --- a/.goreleaser-self.yaml +++ b/.goreleaser-self.yaml @@ -9,7 +9,8 @@ before: - go generate --tags=prod ./... - ./script/completions.sh builds: - - env: + - id: unix + env: - CGO_ENABLED=0 goos: - linux @@ -18,6 +19,18 @@ builds: main: ./cmd/turso flags: - -tags=prod + - id: windows + env: + - CGO_ENABLED=0 + goos: + - windows + goarch: + - amd64 + - arm64 + binary: turso + main: ./cmd/turso + flags: + - -tags=prod release: github: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8392ad4e..155fe1f0 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -9,7 +9,8 @@ before: - go generate --tags=prod ./... - ./script/completions.sh builds: - - env: + - id: unix + env: - CGO_ENABLED=0 goos: - linux @@ -18,6 +19,18 @@ builds: main: ./cmd/turso flags: - -tags=prod + - id: windows + env: + - CGO_ENABLED=0 + goos: + - windows + goarch: + - amd64 + - arm64 + binary: turso + main: ./cmd/turso + flags: + - -tags=prod release: github: diff --git a/README.md b/README.md index 7e70a76a..2cb7ac0e 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,8 @@ turso db destroy The `turso` program keeps settings in your local machine in the following base directory in `turso/settings.json` file: -| OS | Config directory | -| ----- | ----------------------------------------- | -| Linux | `$XDG_CONFIG_HOME` or `$HOME/.config` | -| macOS | `$HOME/Library/Application Support/turso` | +| OS | Config directory | +| ------- | ----------------------------------------- | +| Linux | `$XDG_CONFIG_HOME` or `$HOME/.config` | +| macOS | `$HOME/Library/Application Support/turso` | +| Windows | `%APPDATA%\turso` | diff --git a/internal/cmd/update.go b/internal/cmd/update.go index 51021786..ca164b2c 100644 --- a/internal/cmd/update.go +++ b/internal/cmd/update.go @@ -92,21 +92,3 @@ var updateCmd = &cobra.Command{ return Update() }, } - -func Update() error { - var updateCmd string - - if IsUnderHomebrew() { - updateCmd = "brew update && brew upgrade turso" - } else { - updateCmd = "curl -sSfL \"https://get.tur.so/install.sh\" | sh" - } - command := exec.Command("sh", "-c", updateCmd) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - return fmt.Errorf("failed to execute update command: %w", err) - } - return nil -} diff --git a/internal/cmd/update_unix.go b/internal/cmd/update_unix.go new file mode 100644 index 00000000..d46d9a3f --- /dev/null +++ b/internal/cmd/update_unix.go @@ -0,0 +1,26 @@ +//go:build !windows + +package cmd + +import ( + "fmt" + "os" + "os/exec" +) + +func Update() error { + var updateCmd string + + if IsUnderHomebrew() { + updateCmd = "brew update && brew upgrade turso" + } else { + updateCmd = "curl -sSfL \"https://get.tur.so/install.sh\" | sh" + } + command := exec.Command("sh", "-c", updateCmd) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + if err := command.Run(); err != nil { + return fmt.Errorf("failed to execute update command: %w", err) + } + return nil +} diff --git a/internal/cmd/update_windows.go b/internal/cmd/update_windows.go new file mode 100644 index 00000000..dd516316 --- /dev/null +++ b/internal/cmd/update_windows.go @@ -0,0 +1,13 @@ +//go:build windows + +package cmd + +import "errors" + +func Update() error { + return errors.New( + "automatic updates are not available on Windows; " + + "download the latest Windows ZIP and replace turso.exe: " + + "https://github.com/tursodatabase/turso-cli/releases/latest", + ) +} diff --git a/internal/settings/settings.go b/internal/settings/settings.go index 29ed237f..82c87430 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -3,7 +3,6 @@ package settings import ( "fmt" "os" - "path" "path/filepath" "sync" @@ -55,7 +54,7 @@ func ReadSettings() (*Settings, error) { viper.SetConfigType("json") viper.AddConfigPath(configPath) viper.SetConfigPermissions(settingsFileMode) - configFile := path.Join(configPath, "settings.json") + configFile := filepath.Join(configPath, "settings.json") if abs, err := filepath.Abs(configFile); err == nil { configFile = abs } diff --git a/internal/settings/settings_perm_test.go b/internal/settings/settings_perm_test.go index dbc520fd..99c75862 100644 --- a/internal/settings/settings_perm_test.go +++ b/internal/settings/settings_perm_test.go @@ -3,6 +3,7 @@ package settings import ( "os" "path/filepath" + "runtime" "testing" ) @@ -20,12 +21,14 @@ func TestPersistTightensFilePermissions(t *testing.T) { if err != nil { t.Fatalf("stat after create: %v", err) } - if got := st.Mode().Perm(); got != 0o600 { - t.Errorf("fresh file mode = %o, want 600", got) - } - stDir, _ := os.Stat(dir) - if got := stDir.Mode().Perm(); got != 0o700 { - t.Errorf("fresh dir mode = %o, want 700", got) + if runtime.GOOS != "windows" { + if got := st.Mode().Perm(); got != 0o600 { + t.Errorf("fresh file mode = %o, want 600", got) + } + stDir, _ := os.Stat(dir) + if got := stDir.Mode().Perm(); got != 0o700 { + t.Errorf("fresh dir mode = %o, want 700", got) + } } if err := os.Chmod(file, 0o644); err != nil { @@ -39,7 +42,9 @@ func TestPersistTightensFilePermissions(t *testing.T) { if err != nil { t.Fatalf("stat after persist: %v", err) } - if got := st.Mode().Perm(); got != 0o600 { - t.Errorf("file mode after persist = %o, want 600", got) + if runtime.GOOS != "windows" { + if got := st.Mode().Perm(); got != 0o600 { + t.Errorf("file mode after persist = %o, want 600", got) + } } }