feat: add Windows PowerShell integration and e2e tests #74
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| go-version: ['1.24', '1.25'] | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.BOT_APP_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Run tests | |
| run: go test -v -race -coverprofile=coverage.out ./... | |
| - name: Upload coverage to Codecov | |
| if: matrix.go-version == '1.25' | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| slug: timvw/wt | |
| files: ./coverage.out | |
| fail_ci_if_error: false | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.BOT_APP_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25' | |
| - name: Build | |
| run: go build -v ./... | |
| - name: Build binary | |
| run: | | |
| mkdir -p bin | |
| go build -o bin/wt . | |
| - name: Verify binary | |
| run: | | |
| ./bin/wt --help | |
| file bin/wt | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.BOT_APP_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25' | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v4 | |
| with: | |
| version: latest | |
| cross-compile: | |
| name: Cross Compile | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.BOT_APP_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25' | |
| - name: Cross compile | |
| run: | | |
| mkdir -p bin | |
| GOOS=linux GOARCH=amd64 go build -o bin/wt-linux-amd64 . | |
| GOOS=darwin GOARCH=amd64 go build -o bin/wt-darwin-amd64 . | |
| GOOS=darwin GOARCH=arm64 go build -o bin/wt-darwin-arm64 . | |
| GOOS=windows GOARCH=amd64 go build -o bin/wt-windows-amd64.exe . | |
| - name: Verify binaries | |
| run: | | |
| ls -lh bin/ | |
| file bin/* | |
| e2e-macos: | |
| name: E2E Tests (macOS) | |
| runs-on: macos-latest | |
| strategy: | |
| matrix: | |
| shell: ['bash', 'zsh'] | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.BOT_APP_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25' | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Build wt binary | |
| run: | | |
| mkdir -p bin | |
| go build -o bin/wt . | |
| - name: Verify binary | |
| run: | | |
| ./bin/wt --help | |
| file bin/wt | |
| - name: Run e2e tests for ${{ matrix.shell }} | |
| run: | | |
| # Set up isolated test environment | |
| export ISOLATED_TMPDIR=$(mktemp -d) | |
| export HOME=$ISOLATED_TMPDIR | |
| # Run specific e2e tests (not ./... to avoid redundancy) | |
| if [ "${{ matrix.shell }}" = "bash" ]; then | |
| go test -v -run 'TestE2EAutoCdWithNonInteractiveCommand|TestE2EAutoCdWithCreate' . | |
| elif [ "${{ matrix.shell }}" = "zsh" ]; then | |
| go test -v -run TestE2EAutoCdInZsh . | |
| fi | |
| # Cleanup (best effort - ignore errors from Go cache files) | |
| rm -rf "$ISOLATED_TMPDIR" 2>/dev/null || true | |
| - name: Test shellenv output | |
| run: | | |
| # Capture shellenv output for inspection | |
| ./bin/wt shellenv > shellenv-${{ matrix.shell }}.txt | |
| # Verify shellenv produces output | |
| if [ ! -s shellenv-${{ matrix.shell }}.txt ]; then | |
| echo "ERROR: shellenv produced no output" | |
| exit 1 | |
| fi | |
| # Check for key components (wt function) | |
| if ! grep -q "wt()" shellenv-${{ matrix.shell }}.txt; then | |
| echo "ERROR: wt function not found in shellenv output" | |
| exit 1 | |
| fi | |
| # Verify shell-specific features | |
| if [ "${{ matrix.shell }}" = "bash" ]; then | |
| grep -q "BASH_VERSION" shellenv-${{ matrix.shell }}.txt || (echo "ERROR: bash-specific code not found" && exit 1) | |
| elif [ "${{ matrix.shell }}" = "zsh" ]; then | |
| grep -q "ZSH_VERSION" shellenv-${{ matrix.shell }}.txt || (echo "ERROR: zsh-specific code not found" && exit 1) | |
| fi | |
| echo "shellenv validation passed for ${{ matrix.shell }}" | |
| - name: Test worktree CRUD operations with auto-cd | |
| run: | | |
| # Save absolute paths BEFORE changing directories | |
| WORK_DIR=$(pwd) | |
| WT_BIN="$WORK_DIR/bin/wt" | |
| WT_BIN_DIR="$WORK_DIR/bin" | |
| # Create isolated test environment with separate HOME | |
| ISOLATED_HOME=$(mktemp -d) | |
| WORKTREE_ROOT=$(mktemp -d)/worktrees | |
| TEST_REPO=$(mktemp -d)/test-repo | |
| # Generate shellenv script | |
| $WT_BIN shellenv > /tmp/wt-shellenv-${{ matrix.shell }}.sh | |
| # Create test git repo | |
| mkdir -p $TEST_REPO | |
| cd $TEST_REPO | |
| export HOME=$ISOLATED_HOME | |
| git init | |
| git config user.email "test@example.com" | |
| git config user.name "Test User" | |
| git commit --allow-empty -m "initial commit" | |
| git branch -M main | |
| # Create a test branch | |
| git checkout -b test-branch | |
| git commit --allow-empty -m "test commit" | |
| git checkout main | |
| # Determine shell command | |
| if [ "${{ matrix.shell }}" = "bash" ]; then | |
| SHELL_CMD="bash" | |
| else | |
| SHELL_CMD="zsh" | |
| fi | |
| # Save the current PATH so git is available in the subshell | |
| CURRENT_PATH="$PATH" | |
| # Run test in a subshell with shellenv sourced | |
| $SHELL_CMD -c " | |
| export HOME='$ISOLATED_HOME' | |
| export WORKTREE_ROOT='$WORKTREE_ROOT' | |
| export PATH='$WT_BIN_DIR:$CURRENT_PATH' | |
| cd '$TEST_REPO' | |
| source /tmp/wt-shellenv-${{ matrix.shell }}.sh | |
| echo 'Testing: wt checkout test-branch' | |
| wt checkout test-branch | |
| # Verify we're in the worktree directory | |
| CURRENT_DIR=\$(pwd) | |
| EXPECTED_DIR='$WORKTREE_ROOT/test-repo/test-branch' | |
| if [[ \"\$CURRENT_DIR\" != \"\$EXPECTED_DIR\" ]]; then | |
| echo \"ERROR: Auto-cd failed. Expected: \$EXPECTED_DIR, Got: \$CURRENT_DIR\" | |
| exit 1 | |
| fi | |
| echo \"✓ Auto-cd to worktree verified: \$CURRENT_DIR\" | |
| # Verify worktree was created | |
| wt list | grep test-branch || (echo 'ERROR: worktree not in list' && exit 1) | |
| echo '✓ Worktree created and listed' | |
| # Go back to original repo to test create | |
| cd '$TEST_REPO' | |
| # Test create command (from main repo) | |
| echo 'Testing: wt create feature-test' | |
| wt create feature-test | |
| CURRENT_DIR=\$(pwd) | |
| EXPECTED_DIR='$WORKTREE_ROOT/test-repo/feature-test' | |
| if [[ \"\$CURRENT_DIR\" != \"\$EXPECTED_DIR\" ]]; then | |
| echo \"ERROR: Auto-cd failed for create. Expected: \$EXPECTED_DIR, Got: \$CURRENT_DIR\" | |
| exit 1 | |
| fi | |
| echo \"✓ Auto-cd to new worktree verified: \$CURRENT_DIR\" | |
| # Go back to original repo to test remove | |
| cd '$TEST_REPO' | |
| # Test remove command | |
| echo 'Testing: wt remove test-branch' | |
| wt remove test-branch | |
| # Verify worktree was removed | |
| if wt list | grep test-branch; then | |
| echo 'ERROR: worktree not removed' | |
| exit 1 | |
| fi | |
| echo '✓ Worktree removed successfully' | |
| echo 'CRUD operations with auto-cd test passed!' | |
| " | |
| # Cleanup all temporary directories (best effort) | |
| cd "$WORK_DIR" | |
| rm -rf "$ISOLATED_HOME" "$WORKTREE_ROOT" "$TEST_REPO" /tmp/wt-shellenv-${{ matrix.shell }}.sh 2>/dev/null || true | |
| - name: Upload shellenv output as artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: shellenv-output-${{ matrix.shell }} | |
| path: shellenv-${{ matrix.shell }}.txt | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| - name: Upload test logs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-logs-${{ matrix.shell }} | |
| path: | | |
| /tmp/*.log | |
| /var/tmp/*.log | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| e2e-windows: | |
| name: E2E Tests (Windows) | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| shell: ['powershell', 'pwsh'] | |
| steps: | |
| - name: Generate GitHub App token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.BOT_APP_ID }} | |
| private-key: ${{ secrets.BOT_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.23' | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Build wt binary | |
| run: | | |
| mkdir -p bin | |
| go build -o bin/wt.exe . | |
| - name: Verify binary | |
| run: | | |
| .\bin\wt.exe --help | |
| - name: Install PowerShell Core | |
| if: matrix.shell == 'pwsh' | |
| run: | | |
| # Check if pwsh is already available | |
| if (Get-Command pwsh -ErrorAction SilentlyContinue) { | |
| Write-Output "PowerShell Core already installed" | |
| } else { | |
| Write-Output "Installing PowerShell Core..." | |
| winget install --id Microsoft.PowerShell --source winget --silent --accept-source-agreements --accept-package-agreements | |
| } | |
| shell: powershell | |
| - name: Run Windows e2e tests for ${{ matrix.shell }} | |
| run: | | |
| # Run the appropriate tests based on shell type | |
| if ("${{ matrix.shell }}" -eq "powershell") { | |
| go test -v -run 'TestE2EAutoCdWithPowerShell|TestE2EAutoCdWithCreatePowerShell' . | |
| } else { | |
| # For pwsh, run the same tests but pwsh.exe must be available | |
| go test -v -run 'TestE2EAutoCdWithPowerShell|TestE2EAutoCdWithCreatePowerShell' . | |
| } | |
| shell: powershell | |
| - name: Test shellenv output for ${{ matrix.shell }} | |
| run: | | |
| $shellType = "${{ matrix.shell }}" | |
| # Capture shellenv output for inspection | |
| .\bin\wt.exe shellenv | Out-File -FilePath "shellenv-$shellType.txt" | |
| # Verify shellenv produces output | |
| if (-not (Test-Path "shellenv-$shellType.txt") -or (Get-Item "shellenv-$shellType.txt").Length -eq 0) { | |
| Write-Error "ERROR: shellenv produced no output" | |
| exit 1 | |
| } | |
| # Check for key components (wt function and PowerShell detection) | |
| $content = Get-Content "shellenv-$shellType.txt" -Raw | |
| if ($content -notmatch 'function wt') { | |
| Write-Error "ERROR: wt function not found in shellenv output" | |
| exit 1 | |
| } | |
| if ($content -notmatch '\$PSVersionTable') { | |
| Write-Error "ERROR: PowerShell detection not found in shellenv output" | |
| exit 1 | |
| } | |
| Write-Output "shellenv validation passed for $shellType" | |
| shell: powershell | |
| - name: Test shellenv integration | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| # Set up isolated test environment | |
| $WORK_DIR = (Get-Location).Path | |
| $WT_BIN_DIR = Join-Path $WORK_DIR "bin" | |
| $WT_BIN = Join-Path $WT_BIN_DIR "wt.exe" | |
| $ISOLATED_HOME = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("test-home-" + (Get-Random))) | |
| $WORKTREE_ROOT = Join-Path $env:TEMP ("worktrees-" + (Get-Random)) | |
| $TEST_REPO = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("test-repo-" + (Get-Random))) | |
| $TEST_REPO_NAME = Split-Path -Leaf $TEST_REPO | |
| try { | |
| # Create test git repo | |
| Set-Location $TEST_REPO | |
| $env:HOME = $ISOLATED_HOME.FullName | |
| git init | |
| git config user.email "test@example.com" | |
| git config user.name "Test User" | |
| git commit --allow-empty -m "initial commit" | |
| git branch -M main | |
| # Create a test branch | |
| git checkout -b test-branch | |
| git commit --allow-empty -m "test commit" | |
| git checkout main | |
| # Add bin to PATH (like a real user would) | |
| $env:PATH = "$WT_BIN_DIR;$env:PATH" | |
| # Load shellenv | |
| $env:WORKTREE_ROOT = $WORKTREE_ROOT | |
| Invoke-Expression ((& $WT_BIN shellenv) -join "`n") | |
| # Test checkout | |
| Write-Output "Testing: wt checkout test-branch" | |
| wt checkout test-branch | |
| $CURRENT_DIR = (Get-Location).Path | |
| $EXPECTED_DIR = Join-Path $WORKTREE_ROOT "$TEST_REPO_NAME\test-branch" | |
| if ($CURRENT_DIR -ne $EXPECTED_DIR) { | |
| Write-Error "ERROR: Auto-cd failed. Expected: $EXPECTED_DIR, Got: $CURRENT_DIR" | |
| exit 1 | |
| } | |
| Write-Output "✓ Auto-cd to worktree verified: $CURRENT_DIR" | |
| # Verify worktree was created | |
| $listOutput = wt list | |
| if ($listOutput -notmatch 'test-branch') { | |
| Write-Error "ERROR: worktree not in list" | |
| exit 1 | |
| } | |
| Write-Output "✓ Worktree created and listed" | |
| # Go back to original repo to test create | |
| Set-Location $TEST_REPO | |
| # Test create command | |
| Write-Output "Testing: wt create feature-test" | |
| wt create feature-test | |
| $CURRENT_DIR = (Get-Location).Path | |
| $EXPECTED_DIR = Join-Path $WORKTREE_ROOT "$TEST_REPO_NAME\feature-test" | |
| if ($CURRENT_DIR -ne $EXPECTED_DIR) { | |
| Write-Error "ERROR: Auto-cd failed for create. Expected: $EXPECTED_DIR, Got: $CURRENT_DIR" | |
| exit 1 | |
| } | |
| Write-Output "✓ Auto-cd to new worktree verified: $CURRENT_DIR" | |
| # Go back to original repo to test remove | |
| Set-Location $TEST_REPO | |
| # Test remove command | |
| Write-Output "Testing: wt remove test-branch" | |
| wt remove test-branch | |
| # Verify worktree was removed | |
| $listOutput = wt list | |
| if ($listOutput -match 'test-branch') { | |
| Write-Error "ERROR: worktree not removed" | |
| exit 1 | |
| } | |
| Write-Output "✓ Worktree removed successfully" | |
| Write-Output "CRUD operations with auto-cd test passed!" | |
| } finally { | |
| # Cleanup | |
| Set-Location $WORK_DIR | |
| Remove-Item -Recurse -Force $ISOLATED_HOME -ErrorAction SilentlyContinue | |
| Remove-Item -Recurse -Force $WORKTREE_ROOT -ErrorAction SilentlyContinue | |
| Remove-Item -Recurse -Force $TEST_REPO -ErrorAction SilentlyContinue | |
| } | |
| shell: powershell | |
| - name: Upload shellenv output as artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: shellenv-output-${{ matrix.shell }}-windows | |
| path: shellenv-*.txt | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| - name: Upload test logs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-logs-${{ matrix.shell }}-windows | |
| path: | | |
| ${{ runner.temp }}/*.log | |
| retention-days: 7 | |
| if-no-files-found: ignore |