Skip to content
Open
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
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ cd gogcli
make
```

Windows (PowerShell, no Make/bash required):

```powershell
go build -o .\bin\gog.exe .\cmd\gog
```

Run:

```bash
Expand Down Expand Up @@ -107,6 +113,12 @@ Before adding an account, create OAuth2 credentials from Google Cloud Console:
gog auth credentials ~/Downloads/client_secret_....json
```

Windows PowerShell:

```powershell
.\bin\gog.exe auth credentials "C:\path\to\client_secret_....json"
```

For multiple OAuth clients/projects:

```bash
Expand All @@ -120,6 +132,12 @@ gog auth credentials list
gog auth add you@gmail.com
```

Windows PowerShell:

```powershell
.\bin\gog.exe auth add you@gmail.com
```

This will open a browser window for OAuth authorization. The refresh token is stored securely in your system keychain.

Headless / remote server flows (no browser on the server):
Expand Down Expand Up @@ -175,6 +193,13 @@ export GOG_ACCOUNT=you@gmail.com
gog gmail labels list
```

Windows PowerShell:

```powershell
$env:GOG_ACCOUNT = "you@gmail.com"
.\bin\gog.exe gmail labels list
```

## Authentication & Secrets

### Accounts and tokens
Expand Down Expand Up @@ -557,6 +582,7 @@ Options:
- **Never commit OAuth client credentials** to version control
- Store client credentials outside your project directory
- Use different OAuth clients for development and production
- Rotate the OAuth client secret immediately if it is ever shared or exposed
- Re-authorize with `--force-consent` if you suspect token compromise
- Remove unused accounts with `gog auth remove <email>`

Expand Down Expand Up @@ -1614,6 +1640,13 @@ After cloning, install tools:
make tools
```

Windows (PowerShell) note: the `Makefile` uses POSIX shell commands. If you are not running Git Bash/WSL, use direct Go commands instead:

```powershell
go build -o .\bin\gog.exe .\cmd\gog
go test ./...
```

Pinned tools (installed into `.tools/`):

- Format: `make fmt` (goimports + gofumpt)
Expand Down Expand Up @@ -1645,6 +1678,18 @@ scripts/live-test.sh --account you@gmail.com --skip groups,keep,calendar-enterpr
scripts/live-test.sh --client work --account you@company.com
```

Windows note: use the PowerShell wrapper (it invokes Git Bash when available):

```powershell
.\scripts\live-test.ps1 --fast
```

If you prefer WSL directly:

```powershell
wsl bash scripts/live-test.sh --fast
```

Script toggles:

- `--auth all,groups` to re-auth before running
Expand All @@ -1658,6 +1703,13 @@ Go test wrapper (opt-in):
GOG_LIVE=1 go test -tags=integration ./internal/integration -run Live
```

Windows PowerShell:

```powershell
$env:GOG_LIVE = "1"
go test -tags=integration ./internal/integration -run Live
```

Optional env:
- `GOG_LIVE_FAST=1`
- `GOG_LIVE_SKIP=groups,keep`
Expand Down
5 changes: 5 additions & 0 deletions docs/refactor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Shipped (today)
- `exports.md`: Drive-backed export command pattern (`docs|slides|sheets`).
- `output.md`: shared table + paging helpers.
- `templates.md`: googleauth HTML templates via `//go:embed`.
- Windows compatibility pass:
- `config.ExpandPath` handles both `~/...` and `~\\...`.
- integration live tests support Windows via `scripts/live-test.ps1` wrapper.
- README now documents Windows-native build/auth/live-test flows.
- full `go test ./...` and `govulncheck` validation completed on Windows.

Backlog / next wins

Expand Down
10 changes: 8 additions & 2 deletions internal/config/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,19 @@ func ExpandPath(path string) (string, error) {
return home, nil
}

if strings.HasPrefix(path, "~/") {
if strings.HasPrefix(path, "~/") || strings.HasPrefix(path, "~\\") {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("expand home dir: %w", err)
}

return filepath.Join(home, path[2:]), nil
rel := strings.TrimPrefix(path[2:], string(os.PathSeparator))
rel = strings.TrimLeft(rel, `/\\`)
if rel == "" {
return home, nil
}

return filepath.Join(home, rel), nil
}

return path, nil
Expand Down
10 changes: 10 additions & 0 deletions internal/config/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func TestPaths_CreateDirs(t *testing.T) {
func TestExpandPath(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
if vol := filepath.VolumeName(home); vol != "" {
t.Setenv("HOMEDRIVE", vol)
t.Setenv("HOMEPATH", strings.TrimPrefix(home, vol))
}

tests := []struct {
name string
Expand All @@ -96,6 +101,11 @@ func TestExpandPath(t *testing.T) {
input: "~/Downloads/file.txt",
want: filepath.Join(home, "Downloads/file.txt"),
},
{
name: "tilde with windows-style subpath",
input: "~\\Downloads\\file.txt",
want: filepath.Join(home, "Downloads", "file.txt"),
},
{
name: "absolute path unchanged",
input: "/usr/local/bin",
Expand Down
13 changes: 12 additions & 1 deletion internal/integration/live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
)
Expand All @@ -18,6 +19,16 @@ func TestLiveScript(t *testing.T) {

root := findRepoRoot(t)
script := filepath.Join(root, "scripts", "live-test.sh")
cmdName := script
cmdArgs := []string{}
if runtime.GOOS == "windows" {
script = filepath.Join(root, "scripts", "live-test.ps1")
if _, err := os.Stat(script); err != nil {
t.Skipf("windows live test wrapper not found at %s", script)
}
cmdName = "powershell"
cmdArgs = []string{"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script}
}

args := []string{}
if os.Getenv("GOG_LIVE_FAST") != "" {
Expand All @@ -42,7 +53,7 @@ func TestLiveScript(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
defer cancel()

cmd := exec.CommandContext(ctx, script, args...)
cmd := exec.CommandContext(ctx, cmdName, append(cmdArgs, args...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
Expand Down
39 changes: 39 additions & 0 deletions scripts/live-test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$Args
)

$ErrorActionPreference = "Stop"

$scriptPath = Join-Path $PSScriptRoot "live-test.sh"
if (-not (Test-Path $scriptPath)) {
Write-Error "Could not find live-test.sh at $scriptPath"
exit 1
}

$bashCandidates = @()

$bashCommand = Get-Command bash -ErrorAction SilentlyContinue
if ($bashCommand) {
$bashCandidates += $bashCommand.Source
}

$gitBash = "C:\Program Files\Git\bin\bash.exe"
if (Test-Path $gitBash) {
$bashCandidates += $gitBash
}

$bashCandidates = $bashCandidates | Select-Object -Unique

if ($bashCandidates.Count -eq 0) {
Write-Error "No bash executable found. Install Git for Windows (Git Bash) or run scripts/live-test.sh in WSL."
exit 1
}

$bash = $bashCandidates[0]
& $bash $scriptPath @Args
$exitCode = $LASTEXITCODE
if ($null -eq $exitCode) {
$exitCode = 0
}
exit $exitCode