Skip to content

Commit 3b69285

Browse files
8bitAlexCopilot
andauthored
Env Command (#3)
* feat(env): basic commands * store active env * remove strings (for now) * Write env variables to repo * cleanup * add copilot instructions * remove use command * update readme * Update src/internal/lib/profile.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/internal/lib/env.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent b6614c1 commit 3b69285

22 files changed

Lines changed: 443 additions & 324 deletions

File tree

.github/copilot-instructions.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Raid - Distributed Development Orchestration
2+
3+
## Project Overview
4+
5+
Raid is a Go-based CLI tool that orchestrates development tasks, environments, and dependencies across distributed repositories. It uses YAML/JSON profile configurations to define multi-repo environments and automates setup/execution workflows.
6+
7+
## Architecture
8+
9+
### Core Components
10+
- **CLI Layer**: `src/cmd/` - Cobra-based command structure with subcommands for profiles, installation, and environments
11+
- **Business Logic**: `src/raid/` - High-level API layer that delegates to internal libraries
12+
- **Internal Implementation**: `src/internal/lib/` - Core functionality for profiles, repositories, environments, and task execution
13+
- **System Utilities**: `src/internal/sys/` and `src/utils/` - System-level operations and shared utilities
14+
15+
### Key Design Patterns
16+
17+
#### Configuration Management
18+
- Uses **Viper** for configuration with global state in `src/internal/lib/config.go`
19+
- **Context singleton** pattern: `lib.Context` struct caches active profile and environment
20+
- **Lazy loading**: `Load()` uses cached context, `ForceLoad()` rebuilds from scratch
21+
- Configuration path customizable via `--config/-c` flag
22+
23+
#### Profile System
24+
- Profiles define collections of repositories and environments
25+
- **Multi-document YAML** support using `---` separators for multiple profiles per file
26+
- **JSON Schema validation** against `schemas/raid-profile.schema.json`
27+
- Profile state managed in Viper config under `"profiles"` key
28+
29+
#### Repository Management
30+
- Concurrent cloning with optional thread limits (`--threads/-t` flag)
31+
- Uses Go routines with semaphore pattern for concurrency control
32+
- Repository validation and error aggregation across parallel operations
33+
34+
#### Environment Execution
35+
- Environments contain tasks (Shell commands or Script files) and environment variables
36+
- Task execution supports concurrent execution flag per task
37+
- Environment variables set globally during environment execution
38+
39+
### Key Files & Patterns
40+
41+
#### Entry Points
42+
- `main.go` - Simple delegator to `cmd.Execute()`
43+
- `src/cmd/raid.go` - Root Cobra command with initialization lifecycle
44+
45+
#### Command Structure
46+
```
47+
src/cmd/
48+
├── raid.go # Root command, global flags, initialization
49+
├── profile/ # Profile management (add, list, use, remove)
50+
├── install/ # Repository installation
51+
└── env/ # Environment execution
52+
```
53+
54+
#### Core Business Logic Flow
55+
1. **Initialize**: `raid.Initialize()``lib.InitConfig()``lib.Load()`
56+
2. **Profile Management**: Viper-backed persistence with JSON schema validation
57+
3. **Repository Installation**: Concurrent git cloning with error aggregation
58+
4. **Environment Execution**: Task orchestration with variable setting
59+
60+
### Development Workflows
61+
62+
#### Building & Testing
63+
```bash
64+
go build -o raid # Build binary
65+
go test ./... # Run tests
66+
go test -coverprofile=coverage.out ./... # Generate coverage
67+
```
68+
69+
#### JSON Schema Integration
70+
- Schemas in `schemas/` directory define validation rules
71+
- Use `github.com/santhosh-tekuri/jsonschema/v6` for validation
72+
- YAML language server integration with `# yaml-language-server: $schema=...` comments
73+
74+
#### Configuration Files
75+
- **Profile configs**: YAML/JSON files following `schemas/raid-profile.schema.json`
76+
- **Multi-profile files**: Use YAML `---` document separators
77+
- **Examples**: See `docs/examples/` for reference configurations
78+
79+
### Common Patterns
80+
81+
#### Error Handling
82+
- Use `fmt.Errorf()` for wrapped errors with context
83+
- Aggregate errors from concurrent operations into slices
84+
- CLI commands print errors to stderr via `cmd.PrintErrln()`
85+
86+
#### Concurrent Operations
87+
```go
88+
// Semaphore pattern for limiting concurrency
89+
semaphore := make(chan struct{}, maxThreads)
90+
var wg sync.WaitGroup
91+
errorChan := make(chan error, len(items))
92+
93+
// In goroutine:
94+
semaphore <- struct{}{}
95+
defer func() { <-semaphore }()
96+
```
97+
98+
#### Viper Configuration
99+
- Global config management via `viper.GetString()`, `viper.Set()`
100+
- Nested keys accessed with dot notation: `viper.GetStringMapString("profiles")`
101+
- Automatic config file discovery and loading
102+
103+
### Testing & Quality
104+
- Uses standard Go testing with coverage reporting
105+
- GitHub Actions CI/CD pipeline defined (`.github/workflows/build.yml`)
106+
- Codecov integration for coverage tracking

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*.dll
88
*.so
99
*.dylib
10-
raid
10+
/raid
1111

1212
# Test binary, built with `go test -c`
1313
*.test

README.md

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,63 @@
1-
# Raid - Distributed Development Orchestration
21
[![Build and Test](https://github.com/8bitAlex/raid/actions/workflows/build.yml/badge.svg)](https://github.com/8bitAlex/raid/actions/workflows/build.yml)
32
[![codecov](https://codecov.io/github/8bitAlex/raid/graph/badge.svg?token=Z75V7I2TLW)](https://codecov.io/github/8bitAlex/raid)
43

4+
# Raid - Distributed Development Orchestration
5+
![Windows](https://img.shields.io/badge/Windows-Yes-blue?logo=windows)
6+
![macOS](https://img.shields.io/badge/macOS-Yes-lightgrey?logo=apple)
7+
![Linux](https://img.shields.io/badge/Linux-Yes-yellow?logo=linux)
8+
9+
510
`Raid` is a configurable command-line application that orchestrates common development tasks, environments, and dependencies across distributed code repositories.
611

712
If you have ever pulled a repo (or repos) that require days of configuration just to get a passing build,
813
or have onboarded to a new team that has no documentation, or have a folder of scripts to automate your tasks but haven't
9-
shared them yet, then you are probably a software engineer in need of this.
14+
shared them yet, then you are probably in need of this.
1015

1116
`Raid` handles the pain of error-prone knowledge-dependent tasks and management of your development environment. You no longer need
1217
to worry about wasted time onboarding new contributors. Tribal knowledge can be codified into the repo itself. And you will
1318
never miss running that one test ever again.
1419

1520
📖 For a deeper look at the goals and design of raid, see the [design proposal blog post](https://alexsalerno.dev/blog/raid-design-proposal?utm_source=chatgpt.com).
1621

17-
[Getting Started](#getting-started)[Best Practices](#⚠-best-practices)[Documentation](#usage--documentation)
18-
1922
## Key Features
2023

21-
- **Portable YAML Configurations**: Define your development environments, tasks, and dependencies using simple, version-controlled YAML files.
24+
- **Portable YAML Configurations**: Define your development environments, tasks, and dependencies using simple, version-controlled YAML files. Your configurations live alongside your code, making them easy to share and maintain.
2225
- **Multiple Profiles**: Easily switch between different project setups or team configurations with isolated profiles.
2326
- **Automated Task Execution**: Orchestrate shell commands, scripts, and custom tasks across multiple repositories with a single command.
2427
- **Environment Management**: Define, share, and execute complex development environments to ensure consistency for all contributors.
2528

26-
| Platform | Supported |
27-
|----------|:---------:|
28-
| Linux ||
29-
| Mac ||
30-
| Windows ||
31-
3229
## Development
3330

3431
`Raid` is currently in the **prototype stage**. Core functionality is still being explored and iterated on, so expect frequent changes and incomplete features.
3532

3633
Feedback, issues, and contributions are welcome as the project takes shape.
3734

35+
---
36+
37+
[Getting Started](#getting-started)[Best Practices](#best-practices)[Documentation](#usage--documentation)
38+
39+
---
40+
3841
## Getting Started
3942

4043
### Installation
4144

45+
#### MacOS
46+
47+
```bash
48+
brew install raid # coming soon
49+
```
50+
51+
#### Linux
52+
4253
```bash
43-
# Installation instructions will be added here
54+
# coming soon
55+
```
56+
57+
#### Windows
58+
59+
```bash
60+
# coming soon
4461
```
4562

4663
### Configuration
@@ -57,10 +74,15 @@ raid install # Clone repos and setup environment
5774
raid env dev # Execute development environment (if configured)
5875
```
5976

60-
## ⚠ Best Practices
77+
## Best Practices
78+
79+
### Store sensitive profiles securely
80+
81+
If your raid profile contains sensitive configuration or secrets, keep it in a secure, private location outside of your public codebase.
82+
83+
### Never commit secrets
6184

62-
- **Store profiles securely:** If your raid profile contains sensitive configuration or secrets, keep it in a secure, private location outside of your public codebase.
63-
- **Never commit secrets:** Always keep secrets and credentials in private raid profiles. Do not store them in public repositories.
85+
Always keep secrets and credentials in private raid profiles. Do not store them in public repositories.
6486

6587
## Usage & Documentation
6688

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/spf13/cobra v1.9.1
99
github.com/spf13/viper v1.20.1
1010
gopkg.in/yaml.v3 v3.0.1
11+
github.com/joho/godotenv v1.5.1
1112
)
1213

1314
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
1313
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1414
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1515
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
16+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
17+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
1618
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
1719
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
1820
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=

src/cmd/env/env.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
package env
22

33
import (
4+
"github.com/8bitalex/raid/src/raid"
5+
"github.com/8bitalex/raid/src/raid/env"
46
"github.com/spf13/cobra"
57
)
68

7-
var (
8-
concurrency int
9-
)
10-
119
func init() {
12-
Command.Flags().IntVarP(&concurrency, "threads", "t", 0, "Maximum number of concurrent task executions (0 = unlimited)")
10+
Command.AddCommand(ListEnvCmd)
1311
}
1412

1513
var Command = &cobra.Command{
1614
Use: "env [environment-name]",
1715
Short: "Execute an environment",
1816
Long: "Execute an environment by name. The environment will be searched for in the active profile and all repository configurations. Tasks are executed concurrently and environment variables are set globally.",
19-
Args: cobra.ExactArgs(1),
17+
Args: cobra.RangeArgs(0, 1),
2018
Run: func(cmd *cobra.Command, args []string) {
21-
19+
if len(args) == 0 {
20+
env := env.Get()
21+
if env == "" {
22+
cmd.PrintErrln("No active environment set.")
23+
} else {
24+
cmd.Println("Active environment:", env)
25+
}
26+
} else if len(args) == 1 {
27+
name := args[0]
28+
if !env.Contains(name) {
29+
cmd.PrintErrln("Environment not found:", name)
30+
} else {
31+
cmd.Println("Setting up environment:", name)
32+
if err := env.Set(name); err != nil {
33+
cmd.PrintErrln("Failed to switch environment:", err)
34+
}
35+
raid.ForceLoad()
36+
if err := env.Execute(env.Get()); err != nil {
37+
cmd.PrintErrln("Failed to execute environment:", err)
38+
} else {
39+
cmd.Println("Environment executed successfully.")
40+
}
41+
}
42+
} else {
43+
cmd.PrintErrln("Invalid number of arguments.")
44+
}
2245
},
2346
}

src/cmd/env/list.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/8bitalex/raid/src/raid/env"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var ListEnvCmd = &cobra.Command{
11+
Use: "list",
12+
Short: "List environments",
13+
Run: func(cmd *cobra.Command, args []string) {
14+
envs := env.ListAll()
15+
if len(envs) == 0 {
16+
fmt.Println("No environments found.")
17+
return
18+
}
19+
fmt.Println("Available environments:")
20+
for _, env := range envs {
21+
fmt.Printf("\t%s\n", env)
22+
}
23+
fmt.Print()
24+
},
25+
}

src/cmd/profile/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var ListProfileCmd = &cobra.Command{
1111
Use: "list",
1212
Short: "List profiles",
1313
Run: func(cmd *cobra.Command, args []string) {
14-
profiles := pro.GetAll()
14+
profiles := pro.ListAll()
1515
activeProfile := pro.Get()
1616

1717
if len(profiles) == 0 {

src/cmd/profile/profile.go

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

33
import (
44
"fmt"
5+
"os"
56

67
pro "github.com/8bitalex/raid/src/raid/profile"
78
"github.com/spf13/cobra"
@@ -10,21 +11,31 @@ import (
1011
func init() {
1112
Command.AddCommand(AddProfileCmd)
1213
Command.AddCommand(ListProfileCmd)
13-
Command.AddCommand(UseProfileCmd)
1414
Command.AddCommand(RemoveProfileCmd)
1515
}
1616

1717
var Command = &cobra.Command{
1818
Use: "profile",
1919
Aliases: []string{"p"},
2020
Short: "Manage raid profiles",
21-
Args: cobra.NoArgs,
21+
Args: cobra.RangeArgs(0, 1),
2222
Run: func(cmd *cobra.Command, args []string) {
23-
profile := pro.Get()
24-
if !profile.IsZero() {
25-
fmt.Println(profile.Name)
23+
if len(args) == 0 {
24+
profile := pro.Get()
25+
if !profile.IsZero() {
26+
fmt.Println(profile.Name)
27+
} else {
28+
fmt.Println("No active profile found. Use 'raid profile use <profile>' to set one.")
29+
}
30+
} else if len(args) == 1 {
31+
name := args[0]
32+
if err := pro.Set(name); err != nil {
33+
fmt.Printf("Profile '%s' not found. Use 'raid profile list' to see available profiles.\n", name)
34+
os.Exit(1)
35+
}
36+
fmt.Printf("Profile '%s' is now active.\n", name)
2637
} else {
27-
fmt.Println("No active profile found. Use 'raid profile use <profile>' to set one.")
38+
cmd.PrintErrln("Invalid number of arguments.")
2839
}
2940
},
3041
}

src/cmd/profile/use.go

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)