Skip to content

Commit b0cfb3d

Browse files
authored
Merge pull request #112 from flownative/feature/tty
Fix exec command to work in non-TTY environments
2 parents 7098e35 + c37d3a4 commit b0cfb3d

27 files changed

+326
-162
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/assets/compiled.go
33
/beach
44
go_build_main_go
5+
.claude/settings.local.json

CLAUDE.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Local Beach is a Docker-based development environment for Neos CMS and Flow Framework. It's a CLI tool written in Go that manages local development instances using Docker containers (Nginx, PHP, Redis, and MySQL).
8+
9+
The tool provides commands to initialize, start, stop, and manage Neos/Flow projects locally, with features like resource synchronization with Beach cloud storage, HTTPS setup, and Docker container orchestration.
10+
11+
## Build and Development Commands
12+
13+
### Building the Project
14+
15+
**Full build (with asset generation):**
16+
```bash
17+
make
18+
```
19+
This runs:
20+
- `rm -f assets/compiled.go` - removes compiled assets
21+
- `go generate -v` - generates embedded assets using vfsgen
22+
- `go install -v` - installs dependencies
23+
- `go build -v -ldflags "-X github.com/flownative/localbeach/pkg/version.Version=dev" -o beach` - builds binary
24+
25+
**Quick compile (without regenerating assets):**
26+
```bash
27+
make compile
28+
```
29+
Use this during development when assets haven't changed.
30+
31+
### Testing
32+
33+
Currently no test files exist in the project. When running `go test ./...`, note that there's a known build failure in `cmd/beach/cmd/resource-upload.go:84:3` due to a Printf formatting directive in a logrus.Fatal call.
34+
35+
## Architecture
36+
37+
### Project Structure
38+
39+
- **`cmd/beach/cmd/`** - Cobra-based CLI commands (each command in its own file)
40+
- **`pkg/beachsandbox/`** - Core sandbox abstraction that represents a Local Beach project instance
41+
- **`pkg/exec/`** - Docker command execution wrapper
42+
- **`pkg/path/`** - Platform-specific path handling (Darwin/Linux)
43+
- **`pkg/version/`** - Version information
44+
- **`assets/`** - Embedded template files (Docker Compose configs, env templates, etc.)
45+
46+
### Key Concepts
47+
48+
**BeachSandbox**: The central abstraction representing a Local Beach project. Key responsibilities:
49+
- Project detection via `.localbeach.docker-compose.yaml` marker file
50+
- Environment variable loading from `.localbeach.dist.env`, `.localbeach.env`, `.env` (in that order)
51+
- Flow/Neos installation detection
52+
- Docker Compose file path management
53+
54+
**Project Detection**: Commands traverse up from the current directory looking for `.localbeach.docker-compose.yaml` to find the project root (see `pkg/beachsandbox/helpers.go:detectProjectRootPath`).
55+
56+
**Asset Embedding**: Template files in `assets/` are compiled into the binary using vfsgen. During build, `go generate` runs `assets_generate.go` which embeds all files from `assets/` into `assets/compiled.go`.
57+
58+
### Docker Architecture
59+
60+
Local Beach uses a two-tier Docker setup:
61+
62+
1. **Global infrastructure** (started by `beach start`):
63+
- `local_beach_nginx` - Reverse proxy for all projects
64+
- `local_beach_database` - Shared MySQL database server
65+
- Managed by `assets/local-beach/docker-compose.yml`
66+
67+
2. **Project-specific containers**:
68+
- Defined in `.localbeach.docker-compose.yaml` per project
69+
- Each project gets its own PHP-FPM container and services
70+
- Projects access the database at `http://{project-name}.localbeach.net`
71+
72+
### Important File Locations
73+
74+
**macOS:**
75+
- Base path: `~/Library/Application Support/Flownative/Local Beach/`
76+
77+
**Linux/Other:**
78+
- Base path: `~/.Flownative/Local Beach/`
79+
80+
These paths are defined in `pkg/path/path_darwin.go` and `pkg/path/path_linux.go`.
81+
82+
## Common Development Patterns
83+
84+
### Adding a New Command
85+
86+
1. Create a new file in `cmd/beach/cmd/` (e.g., `my-command.go`)
87+
2. Define a `cobra.Command` struct with Use, Short, Long, Args, and Run fields
88+
3. Add `rootCmd.AddCommand(myCmd)` in the `init()` function
89+
4. Implement the `handleMyCommandRun` function
90+
5. Use `beachsandbox.GetActiveSandbox()` to get the current project context if needed
91+
6. Use `pkg/exec.RunCommand()` or `pkg/exec.RunInteractiveCommand()` for Docker operations
92+
93+
### Environment Variable Handling
94+
95+
Environment files are loaded in order (later files override earlier ones):
96+
1. `.localbeach.dist.env` - Committed defaults
97+
2. `.localbeach.env` - Local overrides
98+
3. `.env` - Additional local config
99+
100+
Variables are parsed and set via `loadLocalBeachEnvironment()` in `pkg/beachsandbox/helpers.go`.
101+
102+
### Resource Path Calculation
103+
104+
Flow/Neos persistent resources use a hash-based directory structure. The `getRelativePersistentResourcePathByHash()` function in `cmd/beach/cmd/helpers.go` converts resource hashes to their filesystem path structure.
105+
106+
### Docker Command Execution and TTY Detection
107+
108+
The `pkg/exec` package provides two methods for executing Docker commands:
109+
110+
- **`RunInteractiveCommand()`**: Connects stdin/stdout/stderr for interactive sessions
111+
- **`RunCommand()`**: Captures output without connecting stdin
112+
113+
**TTY Detection Pattern** (see `cmd/beach/cmd/exec.go`):
114+
115+
Commands that need to work both interactively (user's terminal) and programmatically (automation tools, Claude Code) should detect TTY availability:
116+
117+
```go
118+
// Use syscall.TIOCGETA ioctl for reliable TTY detection
119+
var termios syscall.Termios
120+
_, _, errno := syscall.Syscall6(syscall.SYS_IOCTL, os.Stdin.Fd(),
121+
syscall.TIOCGETA, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
122+
isTTY := errno == 0
123+
```
124+
125+
This is more reliable than `os.Stat().Mode() & os.ModeCharDevice` which can incorrectly report TTY availability.
126+
127+
**Docker Exec Flags**:
128+
- Interactive (TTY available): Use `-t -i` flags (allocate pseudo-TTY + keep stdin open)
129+
- Non-interactive: Omit all flags (Docker's `-t` flag requires a real TTY)
130+
131+
**Error Handling**: When using `RunCommand()` in non-TTY mode, print output before checking errors so users see command output even on failure.
132+
133+
## Version Management
134+
135+
The version is injected at build time via ldflags:
136+
```
137+
-ldflags "-X github.com/flownative/localbeach/pkg/version.Version=dev"
138+
```
139+
140+
For releases, replace "dev" with the actual version number.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ For a slightly quicker build, use `make compile`.
4747
This library was developed by Robert Lemke with major contributions by Karsten Dambekalns and Christian Müller. Feel
4848
free to suggest new features, report bugs or provide bug fixes in our GitHub project.
4949

50-
Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller, licensed under the Apache License, version 2.0.
50+
Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller, licensed under the Apache License, version 2.0.

cmd/beach/cmd/down.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller
1+
// Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

cmd/beach/cmd/exec.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller
1+
// Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -16,11 +16,15 @@ package cmd
1616

1717
import (
1818
"fmt"
19+
"os"
20+
"strings"
21+
"syscall"
22+
"unsafe"
23+
1924
"github.com/flownative/localbeach/pkg/beachsandbox"
2025
"github.com/flownative/localbeach/pkg/exec"
2126
log "github.com/sirupsen/logrus"
2227
"github.com/spf13/cobra"
23-
"strings"
2428
)
2529

2630
// execCmd represents the exec command
@@ -43,17 +47,36 @@ func handleExecRun(cmd *cobra.Command, args []string) {
4347
return
4448
}
4549

46-
commandArgs := []string{"exec", "-ti", sandbox.ProjectName + "_php"}
50+
// Check if stdin is a TTY using syscall (more reliable than Mode check)
51+
var termios syscall.Termios
52+
_, _, errno := syscall.Syscall6(syscall.SYS_IOCTL, os.Stdin.Fd(), syscall.TIOCGETA, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
53+
isTTY := errno == 0
54+
55+
// Build Docker exec command with appropriate flags
56+
commandArgs := []string{"exec"}
57+
if isTTY {
58+
commandArgs = append(commandArgs, "-t", "-i")
59+
}
60+
// Note: No -i flag when not TTY since stdin isn't connected in RunCommand
61+
commandArgs = append(commandArgs, sandbox.ProjectName+"_php")
4762
if len(args) > 0 {
4863
commandArgs = append(commandArgs, "bash", "-l", "-c", strings.Trim(fmt.Sprint(args), "[]"))
4964
} else {
5065
commandArgs = append(commandArgs, "bash")
5166
}
5267

53-
err = exec.RunInteractiveCommand("docker", commandArgs)
54-
if err != nil {
55-
log.Fatal(err)
56-
return
68+
// Use the appropriate execution method based on TTY detection
69+
if isTTY {
70+
err = exec.RunInteractiveCommand("docker", commandArgs)
71+
if err != nil {
72+
log.Fatal(err)
73+
return
74+
}
75+
} else {
76+
output, err := exec.RunCommand("docker", commandArgs)
77+
fmt.Print(output)
78+
if err != nil {
79+
os.Exit(1)
80+
}
5781
}
58-
return
5982
}

cmd/beach/cmd/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller
1+
// Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

cmd/beach/cmd/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller
1+
// Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

cmd/beach/cmd/logs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller
1+
// Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

cmd/beach/cmd/pause.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019-2024 Robert Lemke, Karsten Dambekalns, Christian Müller
1+
// Copyright 2019-2025 Robert Lemke, Karsten Dambekalns, Christian Müller
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)