diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6c82b7b5f..24d183627 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -110,38 +110,14 @@ jobs: GOFLAGS: '-buildvcs=false' release: - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') + runs-on: windows-latest needs: [build-and-test, sast-scan] + if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Install GPG - run: sudo apt-get update && sudo apt-get install -y gnupg - - - name: Install Aqua - uses: aquaproj/aqua-installer@5e54e5cee8a95ee2ce7c04cb993da6dfad13e59c # v3.1.2 - with: - aqua_version: v2.51.1 - - - name: Install tools - run: aqua install - - - name: Cache Go Modules - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Install Dependencies - run: go install ./... - - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 @@ -149,14 +125,24 @@ jobs: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} + - name: Check if prerelease + id: prerelease + run: | + if [[ "${{ github.ref }}" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+- ]]; then + echo "prerelease=true" >> $GITHUB_OUTPUT + else + echo "prerelease=false" >> $GITHUB_OUTPUT + fi + shell: bash + - name: Run GoReleaser uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: version: "~> v2" - args: release --clean + args: release --clean ${{ steps.prerelease.outputs.prerelease == 'true' && '--skip=chocolatey,homebrew' || '' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GPG_FINGERPRINT: ${{ env.GPG_FINGERPRINT }} - HOMEBREW_CLI_WRITE_PAT: ${{ secrets.HOMEBREW_CLI_WRITE_PAT }} GITHUB_SHA: ${{ github.sha }} - SKIP_HOMEBREW: ${{ steps.prerelease.outputs.prerelease == 'true' }} + HOMEBREW_CLI_WRITE_PAT: ${{ secrets.HOMEBREW_CLI_WRITE_PAT }} + CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4bc428f6e..901056dcc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -29,7 +29,11 @@ builds: # Archive configuration archives: - id: windsor - formats: ["tar.gz"] + formats: + - tar.gz + format_overrides: + - goos: windows + format: zip changelog: sort: asc @@ -61,14 +65,59 @@ signs: brews: - name: windsor - directory: Formula - skip_upload: "{{ eq .Env.SKIP_HOMEBREW \"true\" }}" repository: owner: windsorcli name: homebrew-cli branch: main token: "{{ .Env.HOMEBREW_CLI_WRITE_PAT }}" - homepage: "https://windsorcli.github.io" + commit_author: + name: goreleaserbot + email: bot@goreleaser.com + homepage: "https://windsorcli.github.io" description: "The Windsor Command Line Interface" + license: "MPL-2.0" + skip_upload: auto + download_strategy: GithubPrivateRepositoryReleaseDownloadStrategy + custom_require: "lib/custom_download_strategy" + install: | + bin.install "windsor" + # Install shell completions + output = Utils.safe_popen_read("#{bin}/windsor", "completion", "bash") + (bash_completion/"windsor").write output + output = Utils.safe_popen_read("#{bin}/windsor", "completion", "zsh") + (zsh_completion/"_windsor").write output + output = Utils.safe_popen_read("#{bin}/windsor", "completion", "fish") + (fish_completion/"windsor.fish").write output + +chocolateys: + - name: windsor ids: - windsor + package_source_url: https://github.com/windsorcli/cli + owners: Windsor CLI + title: Windsor CLI + authors: Windsor CLI Team + project_url: https://windsorcli.github.io + icon_url: https://windsorcli.github.io/icon.png + copyright: "2025 Windsor CLI Team" + license_url: https://github.com/windsorcli/cli/blob/main/LICENSE + require_license_acceptance: false + project_source_url: https://github.com/windsorcli/cli + docs_url: https://windsorcli.github.io + bug_tracker_url: https://github.com/windsorcli/cli/issues + tags: "cli windows command-line" + summary: "The Windsor Command Line Interface" + description: | + The Windsor CLI assists your cloud native development workflow. + This package provides the Windows installer for Windsor CLI. + + After installation, add the following line to your PowerShell profile to enable shell integration: + ```powershell + Invoke-Expression (& windsor hook powershell) + ``` + + Your PowerShell profile is located at: $PROFILE + release_notes: "https://github.com/windsorcli/cli/releases/tag/v{{ .Version }}" + api_key: "{{ .Env.CHOCOLATEY_API_KEY }}" + source_repo: "https://push.chocolatey.org/" + url_template: "https://github.com/windsorcli/cli/releases/download/v{{ .Version }}/windsor_{{ .Version }}_windows_amd64.zip" diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index dcbe1f2e2..544e2b2f3 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -2238,16 +2238,19 @@ func TestBaseController_createConfigComponent(t *testing.T) { // Clear any existing config handler mocks.Injector.Register("configHandler", nil) - // Set environment variable to force config load - oldEnv := os.Getenv("WINDSORCONFIG") - os.Setenv("WINDSORCONFIG", "test.yaml") - defer func() { - if oldEnv != "" { - os.Setenv("WINDSORCONFIG", oldEnv) - } else { - os.Unsetenv("WINDSORCONFIG") - } - }() + // Create temporary project directory + projectRoot := t.TempDir() + mockShell := shell.NewMockShell() + mockShell.GetProjectRootFunc = func() (string, error) { + return projectRoot, nil + } + mocks.Injector.Register("shell", mockShell) + + // Create config file + configPath := filepath.Join(projectRoot, "windsor.yaml") + if err := os.WriteFile(configPath, []byte("test"), 0644); err != nil { + t.Fatalf("Failed to create test config file: %v", err) + } // When creating the config component err := controller.createConfigComponent(Requirements{}) @@ -2285,6 +2288,57 @@ func TestBaseController_createConfigComponent(t *testing.T) { t.Errorf("Expected no error, got %v", err) } }) + + t.Run("CreatesConfigComponent", func(t *testing.T) { + // Given a controller with a config handler + controller, mocks := setup(t) + mockConfigHandler := config.NewMockConfigHandler() + controller.constructors.NewConfigHandler = func(di.Injector) config.ConfigHandler { + return mockConfigHandler + } + + // Clear any existing config handler + mocks.Injector.Register("configHandler", nil) + + // When creating the config component + err := controller.createConfigComponent(Requirements{}) + + // Then no error should be returned + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + // And the config handler should be registered + if mocks.Injector.Resolve("configHandler") != mockConfigHandler { + t.Error("Expected config handler to be registered") + } + }) + + t.Run("HandlesConfigHandlerError", func(t *testing.T) { + // Given a controller with a failing config handler + controller, mocks := setup(t) + mockConfigHandler := config.NewMockConfigHandler() + mockConfigHandler.InitializeFunc = func() error { + return fmt.Errorf("config handler error") + } + controller.constructors.NewConfigHandler = func(di.Injector) config.ConfigHandler { + return mockConfigHandler + } + + // Clear any existing config handler + mocks.Injector.Register("configHandler", nil) + + // When creating the config component + err := controller.createConfigComponent(Requirements{}) + + // Then an error should be returned + if err == nil { + t.Error("Expected error, got nil") + } + if !strings.Contains(err.Error(), "config handler error") { + t.Errorf("Expected error about config handler, got: %v", err) + } + }) } func TestBaseController_createSecretsComponents(t *testing.T) {