Skip to content

Commit fb7bbf1

Browse files
rubysclaude
andcommitted
Simplify CLI: Make --root and --config optional
- Renamed --rails-root to --root for brevity - Made --root optional, defaults to current directory (".") - Made --config optional, auto-loads config/navigator.yml if present - Updated all documentation and tests to reflect these changes This makes Navigator easier to use - just run "navigator serve" in your Rails app directory and it will automatically use the current directory as root and load config/navigator.yml if it exists. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3b5e8ae commit fb7bbf1

11 files changed

Lines changed: 101 additions & 52 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
echo '"2025": {}' > /tmp/test-rails/config/tenant/showcases.yml
4848
4949
# Test that binary starts and exits cleanly with new CLI syntax
50-
timeout 5s ./navigator serve --rails-root /tmp/test-rails --listen :0 || [ $? -eq 124 ]
50+
timeout 5s ./navigator serve --root /tmp/test-rails --listen :0 || [ $? -eq 124 ]
5151
5252
build:
5353
runs-on: ubuntu-latest

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ jobs:
138138
echo "" >> release_notes.md
139139
echo "Extract the binary and run:" >> release_notes.md
140140
echo "\`\`\`bash" >> release_notes.md
141-
echo "./navigator serve --rails-root /path/to/your/rails/app" >> release_notes.md
141+
echo "./navigator serve --root /path/to/your/rails/app" >> release_notes.md
142142
echo "\`\`\`" >> release_notes.md
143143
fi
144144

CLAUDE.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,29 @@ Navigator is a modern Go-based web server that replaces nginx/Passenger for mult
6565
go build -o navigator cmd/navigator/main.go
6666

6767
# Run with command-line arguments
68-
./navigator serve --rails-root /path/to/rails/app --listen :3000
68+
./navigator serve --root /path/to/rails/app --listen :3000
6969

70-
# Run with configuration file
70+
# Run in current directory (uses '.' as default)
71+
./navigator serve
72+
73+
# Run with configuration file (automatically looks for config/navigator.yml)
74+
./navigator serve
75+
76+
# Run with custom configuration file
7177
./navigator serve --config configs/navigator.yaml
7278

7379
# Run with environment variables
7480
NAVIGATOR_RAILS_ROOT=/path/to/app ./navigator serve
7581

7682
# Build and run in one command
77-
go build -o navigator cmd/navigator/main.go && ./navigator serve --rails-root /Users/rubys/git/showcase
83+
go build -o navigator cmd/navigator/main.go && ./navigator serve --root /Users/rubys/git/showcase
7884

7985
# View help and available commands
8086
./navigator --help
8187
./navigator serve --help
8288

8389
# Validate configuration
84-
./navigator config validate --rails-root /path/to/app
90+
./navigator config validate --root /path/to/app
8591
```
8692

8793
### Development Workflow
@@ -146,12 +152,14 @@ When a Puma process dies, Navigator automatically:
146152

147153
### Configuration Methods
148154

149-
Navigator supports three configuration methods (in order of precedence):
155+
Navigator supports three configuration methods (in order of precedence).
156+
157+
**Note**: When no `--config` flag is provided, Navigator automatically looks for `config/navigator.yml` relative to the root directory.
150158

151159
1. **Command-line flags** (highest priority):
152160
```bash
153161
./navigator serve \
154-
--rails-root /path/to/rails/app \
162+
--root /path/to/rails/app \
155163
--listen :3000 \
156164
--url-prefix /showcase \
157165
--max-puma 20 \
@@ -171,8 +179,11 @@ export NAVIGATOR_AUTH_HTPASSWD_FILE="/path/to/htpasswd"
171179
```
172180

173181
3. **YAML configuration file** (lowest priority):
182+
183+
Navigator automatically checks for `config/navigator.yml` relative to the root directory. You can also create a custom configuration file:
184+
174185
```yaml
175-
# config/navigator.yaml
186+
# config/navigator.yml (or custom location)
176187
server:
177188
listen: ":3000"
178189
url_prefix: "/showcase"
@@ -276,7 +287,7 @@ ab -n 100 -c 5 -A username:password http://localhost:3000/2025/raleigh/disney/
276287
Navigator logs all requests and process management operations:
277288

278289
```bash
279-
./navigator -rails-root /path/to/app 2>&1 | tee navigator.log
290+
./navigator serve --root /path/to/app 2>&1 | tee navigator.log
280291
```
281292

282293
### Common Issues

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ build: ## Build the binary
1313

1414
# Development targets
1515
dev: ## Build and run in development mode
16-
go run cmd/navigator/main.go -rails-root $(PWD) -log-level debug
16+
go run cmd/navigator/main.go serve --log-level debug
1717

1818
clean: ## Clean build artifacts
1919
rm -f ${BINARY_NAME}
@@ -70,7 +70,7 @@ watch: ## Watch files and rebuild on changes (requires entr)
7070

7171
profile: ## Run with profiling enabled
7272
go build ${LDFLAGS} -o ${BINARY_NAME} cmd/navigator/main.go
73-
./${BINARY_NAME} -rails-root $(PWD) -log-level debug -cpuprofile cpu.prof -memprofile mem.prof
73+
./${BINARY_NAME} serve --log-level debug -cpuprofile cpu.prof -memprofile mem.prof
7474

7575
# Help
7676
help: ## Show this help message

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,18 @@ go build -o navigator cmd/navigator/main.go
4343

4444
```bash
4545
# Start Navigator with command-line flags
46-
./navigator serve --rails-root /path/to/rails/app --listen :3000
46+
./navigator serve --root /path/to/rails/app --listen :3000
47+
48+
# Or start in the current directory (uses '.' as default)
49+
./navigator serve
4750

4851
# Or use environment variables
4952
NAVIGATOR_RAILS_ROOT=/path/to/rails/app ./navigator serve
5053

51-
# Or use a configuration file
54+
# Or use a configuration file (automatically looks for config/navigator.yml)
55+
./navigator serve
56+
57+
# Or specify a custom configuration file
5258
./navigator serve --config /path/to/navigator.yaml
5359

5460
# Get help and see all available commands
@@ -64,7 +70,7 @@ Navigator supports three configuration methods (in order of precedence):
6470

6571
```bash
6672
./navigator serve \
67-
--rails-root /path/to/rails/app \
73+
--root /path/to/rails/app \
6874
--listen :3000 \
6975
--url-prefix /showcase \
7076
--max-puma 20 \
@@ -91,7 +97,7 @@ export NAVIGATOR_LOGGING_LEVEL="debug"
9197

9298
### 3. YAML Configuration File (Lowest Priority)
9399

94-
Create a `navigator.yaml` file:
100+
Navigator automatically looks for `config/navigator.yml` relative to the root directory. You can also create a custom `navigator.yaml` file:
95101

96102
```yaml
97103
server:
@@ -117,14 +123,18 @@ logging:
117123
118124
Then run:
119125
```bash
126+
# Uses config/navigator.yml if present in root directory
127+
./navigator serve
128+
129+
# Or specify a custom config file
120130
./navigator serve --config navigator.yaml
121131
```
122132

123133
### Configuration Options
124134

125135
| Option | CLI Flag | Environment Variable | Default | Description |
126136
|--------|----------|---------------------|---------|-------------|
127-
| Rails Root | `--rails-root` | `NAVIGATOR_RAILS_ROOT` | *required* | Rails application directory |
137+
| Rails Root | `--root` | `NAVIGATOR_RAILS_ROOT` | `.` | Application root directory |
128138
| Listen Address | `--listen` | `NAVIGATOR_SERVER_LISTEN` | `:3000` | HTTP server bind address |
129139
| URL Prefix | `--url-prefix` | `NAVIGATOR_SERVER_URL_PREFIX` | `/showcase` | URL prefix to strip |
130140
| Max Puma | `--max-puma` | `NAVIGATOR_MANAGER_MAX_PUMA` | `10` | Max concurrent Puma processes |

internal/cli/commands_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cli
22

33
import (
4-
"os"
54
"strings"
65
"testing"
76

@@ -55,7 +54,7 @@ func TestVersionCommand(t *testing.T) {
5554
func TestServeCommandFlags(t *testing.T) {
5655
// Test that serve command has all expected flags
5756
expectedFlags := []string{
58-
"rails-root",
57+
"root",
5958
"listen",
6059
"url-prefix",
6160
"max-puma",
@@ -88,7 +87,7 @@ func TestServeCommandFlags(t *testing.T) {
8887
func TestConfigValidateCommandFlags(t *testing.T) {
8988
// Test that config validate command has required flags
9089
expectedFlags := []string{
91-
"rails-root",
90+
"root",
9291
"config",
9392
}
9493

@@ -176,9 +175,8 @@ func TestGetConfigInitialization(t *testing.T) {
176175
config = originalConfig
177176
}()
178177

179-
// Set minimal required environment for config loading
180-
os.Setenv("NAVIGATOR_RAILS_ROOT", "/tmp")
181-
defer os.Unsetenv("NAVIGATOR_RAILS_ROOT")
178+
// No longer need to set NAVIGATOR_RAILS_ROOT since root has a default value
179+
// The default '.' will be used
182180

183181
// GetConfig should initialize config without panicking
184182
testConfig := GetConfig()

internal/cli/config.go

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"fmt"
5+
"os"
56
"path/filepath"
67
"strings"
78
"time"
@@ -76,18 +77,36 @@ func LoadConfig(configFile string) (*Config, error) {
7677
return nil, fmt.Errorf("error reading config file: %w", err)
7778
}
7879
} else {
79-
// Try to find config file in common locations
80-
v.SetConfigName("navigator")
81-
v.SetConfigType("yaml")
82-
v.AddConfigPath("./config") // Only look in project config directory
83-
v.AddConfigPath("/etc/navigator")
84-
85-
// Read config file if found (ignore if not found or corrupted)
86-
if err := v.ReadInConfig(); err != nil {
87-
// Only return error if explicitly set config file, otherwise just warn and continue
88-
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
80+
// First get the root directory from viper (could be from flag or env)
81+
rootDir := v.GetString("rails.root")
82+
if rootDir == "" || rootDir == "." {
83+
rootDir, _ = filepath.Abs(".")
84+
} else {
85+
rootDir, _ = filepath.Abs(rootDir)
86+
}
87+
88+
// Check for config/navigator.yml relative to root directory
89+
defaultConfigPath := filepath.Join(rootDir, "config", "navigator.yml")
90+
if _, err := os.Stat(defaultConfigPath); err == nil {
91+
v.SetConfigFile(defaultConfigPath)
92+
if err := v.ReadInConfig(); err != nil {
8993
// Config file found but has errors - warn but continue
90-
fmt.Printf("Warning: found config file but couldn't parse it: %v\n", err)
94+
fmt.Printf("Warning: found config file at %s but couldn't parse it: %v\n", defaultConfigPath, err)
95+
}
96+
} else {
97+
// Try to find config file in common locations
98+
v.SetConfigName("navigator")
99+
v.SetConfigType("yaml")
100+
v.AddConfigPath("./config") // Look in local config directory
101+
v.AddConfigPath("/etc/navigator")
102+
103+
// Read config file if found (ignore if not found or corrupted)
104+
if err := v.ReadInConfig(); err != nil {
105+
// Only return error if explicitly set config file, otherwise just warn and continue
106+
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
107+
// Config file found but has errors - warn but continue
108+
fmt.Printf("Warning: found config file but couldn't parse it: %v\n", err)
109+
}
91110
}
92111
}
93112
}
@@ -113,6 +132,7 @@ func setDefaults(v *viper.Viper) {
113132
v.SetDefault("server.url_prefix", "/showcase")
114133

115134
// Rails defaults
135+
v.SetDefault("rails.root", ".")
116136
v.SetDefault("rails.showcases", "config/tenant/showcases.yml")
117137
v.SetDefault("rails.db_path", "db")
118138
v.SetDefault("rails.storage", "storage")
@@ -127,9 +147,9 @@ func setDefaults(v *viper.Viper) {
127147

128148
// validateAndResolvePaths validates required fields and resolves relative paths
129149
func (c *Config) validateAndResolvePaths() error {
130-
// Validate required fields
150+
// Use current directory if Rails.Root is empty
131151
if c.Rails.Root == "" {
132-
return fmt.Errorf("rails.root is required")
152+
c.Rails.Root = "."
133153
}
134154

135155
// Resolve Rails root to absolute path

internal/cli/config_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,16 +199,22 @@ func TestValidateAndResolvePaths(t *testing.T) {
199199
}
200200
}
201201

202-
func TestValidateAndResolvePathsRequiredField(t *testing.T) {
202+
func TestValidateAndResolvePathsDefaultRoot(t *testing.T) {
203203
config := &Config{
204204
Rails: RailsConfig{
205-
Root: "", // Missing required field
205+
Root: "", // Empty root should default to "."
206206
},
207207
}
208208

209209
err := config.validateAndResolvePaths()
210-
if err == nil {
211-
t.Error("Expected error for missing rails.root, but got none")
210+
if err != nil {
211+
t.Errorf("Unexpected error: %v", err)
212+
}
213+
214+
// Verify that empty root was replaced with "."
215+
expectedRoot, _ := filepath.Abs(".")
216+
if config.Rails.Root != expectedRoot {
217+
t.Errorf("Expected root to be '%s', got '%s'", expectedRoot, config.Rails.Root)
212218
}
213219
}
214220

internal/cli/config_validate.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This command will:
2424
Examples:
2525
navigator config validate
2626
navigator config validate --config /etc/navigator/navigator.yaml
27-
navigator config validate --rails-root /path/to/rails/app`,
27+
navigator config validate --root /path/to/rails/app`,
2828
Run: func(cmd *cobra.Command, args []string) {
2929
// Load configuration - flag binding should work with global Viper instance
3030
cfg, err := LoadConfig(cfgFile)
@@ -47,8 +47,7 @@ func init() {
4747
rootCmd.AddCommand(configCmd)
4848
configCmd.AddCommand(configValidateCmd)
4949

50-
// Mark rails-root as required for config validate command
51-
configValidateCmd.MarkPersistentFlagRequired("rails-root")
50+
// No longer marking root as required since it has a default value
5251
}
5352

5453
// validateConfig validates and displays the current configuration

internal/cli/root.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ func init() {
4646
// Let individual commands initialize config as needed
4747

4848
// Global flags
49-
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is navigator.yaml)")
49+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: config/navigator.yml relative to root)")
5050

5151
// Server flags
5252
rootCmd.PersistentFlags().String("listen", ":3000", "address to listen on")
5353
rootCmd.PersistentFlags().String("url-prefix", "/showcase", "URL prefix to strip from requests")
5454

5555
// Rails flags
56-
rootCmd.PersistentFlags().String("rails-root", "", "Rails application root directory (required)")
57-
rootCmd.PersistentFlags().String("showcases", "config/tenant/showcases.yml", "path to showcases.yml relative to rails-root")
56+
rootCmd.PersistentFlags().String("root", ".", "Application root directory (default: current directory)")
57+
rootCmd.PersistentFlags().String("showcases", "config/tenant/showcases.yml", "path to showcases.yml relative to root")
5858
rootCmd.PersistentFlags().String("db-path", "db", "database directory path")
5959
rootCmd.PersistentFlags().String("storage", "storage", "storage directory path")
6060

@@ -82,7 +82,7 @@ func bindFlags() {
8282
viper.BindPFlag("server.url_prefix", rootCmd.PersistentFlags().Lookup("url-prefix"))
8383

8484
// Rails flags
85-
viper.BindPFlag("rails.root", rootCmd.PersistentFlags().Lookup("rails-root"))
85+
viper.BindPFlag("rails.root", rootCmd.PersistentFlags().Lookup("root"))
8686
viper.BindPFlag("rails.showcases", rootCmd.PersistentFlags().Lookup("showcases"))
8787
viper.BindPFlag("rails.db_path", rootCmd.PersistentFlags().Lookup("db-path"))
8888
viper.BindPFlag("rails.storage", rootCmd.PersistentFlags().Lookup("storage"))

0 commit comments

Comments
 (0)