Skip to content

Commit c2ff5ec

Browse files
Setup template generation
1 parent 9f5daa5 commit c2ff5ec

28 files changed

Lines changed: 1476 additions & 243 deletions

.initiat/config.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
org: initiat
2-
project: initiat-cli
1+
name: "initiat-cli"

.initiat/setup.yml

Lines changed: 33 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,40 @@
11
version: 1
2-
name: "Initiat CLI Development Setup"
3-
description: "Set up development environment for building Initiat CLI locally"
4-
2+
name: initiat-cli
3+
description: Generated setup for this project
54
matrix:
6-
os: [macos, linux, windows]
7-
arch: [x86_64, arm64]
8-
5+
os:
6+
- macos
7+
- linux
8+
- windows
9+
arch:
10+
- x86_64
11+
- arm64
912
defaults:
10-
timeout: "15m"
11-
shell: auto
12-
continue_on_error: false
13-
cwd: "."
14-
15-
bootstrap:
16-
- name: "Install Homebrew (macOS)"
17-
if: os("macos") && !cmd_ok("command -v brew")
18-
run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
19-
20-
- name: "Update apt (Linux)"
21-
if: os("linux")
22-
run: sudo apt-get update
23-
24-
- name: "Install Git (macOS)"
25-
if: os("macos") && !cmd_ok("command -v git")
26-
run: brew install git
27-
28-
- name: "Install Git (Linux)"
29-
if: os("linux") && !cmd_ok("command -v git")
30-
run: sudo apt-get install -y git
31-
32-
- name: "Install Git (Windows)"
33-
if: os("windows") && !cmd_ok("where git")
34-
run: choco install git -y
35-
36-
- name: "Install asdf (macOS/Linux)"
37-
if: '!os("windows") && !cmd_ok("command -v asdf")'
38-
run: |
39-
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 || true
40-
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
41-
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.zshrc || true
42-
. "$HOME/.asdf/asdf.sh"
43-
13+
timeout: 15m
14+
shell: auto
15+
cwd: .
4416
provision:
45-
- name: "Install Go via asdf"
46-
if: '!os("windows") && !cmd_ok("go --version")'
47-
run: |
48-
. "$HOME/.asdf/asdf.sh" || true
49-
asdf plugin add golang || true
50-
asdf install golang 1.25.7
51-
asdf global golang 1.25.7
52-
53-
- name: "Install Go via Chocolatey (Windows)"
54-
if: os("windows") && !cmd_ok("go --version")
55-
run: choco install golang -y
56-
17+
- name: Verify Go is installed
18+
if: '!cmd_ok("go version")'
19+
print: |
20+
Go is not installed. Install it from https://go.dev/dl/ or use your package manager:
21+
macOS: brew install go
22+
Linux: sudo apt-get install golang-go (or equivalent)
23+
Windows: choco install golang
5724
setup:
58-
- name: "Download Go dependencies"
59-
run: go mod download
60-
61-
- name: "Verify Go modules"
62-
run: go mod verify
63-
64-
- name: "Install Linux X11 libraries for clipboard support"
65-
if: os("linux")
66-
run: |
67-
if command -v apt-get &> /dev/null; then
68-
sudo apt-get update && sudo apt-get install -y libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev
69-
elif command -v yum &> /dev/null; then
70-
sudo yum install -y libX11-devel libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel
71-
elif command -v dnf &> /dev/null; then
72-
sudo dnf install -y libX11-devel libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel
73-
elif command -v pacman &> /dev/null; then
74-
sudo pacman -S --noconfirm libx11 libxrandr libxinerama libxcursor libxi
75-
fi
76-
25+
- name: Download Go dependencies
26+
if: file_exists("go.mod")
27+
run: go mod download
28+
- name: Verify Go modules
29+
if: file_exists("go.mod")
30+
run: go mod verify
7731
verify:
78-
- name: "Verify Go version"
79-
run: go version || exit 1
80-
81-
- name: "Verify Go modules are available"
82-
run: go list -m all || exit 1
83-
84-
- name: "Build the project"
85-
run: go build -o initiat .
86-
87-
- name: "Verify binary was created (Unix)"
88-
if: '!os("windows")'
89-
run: test -f initiat && echo "Binary created successfully"
90-
91-
- name: "Verify binary was created (Windows)"
92-
if: os("windows")
93-
run: if (Test-Path initiat.exe) { Write-Host "Binary created successfully" } else { exit 1 }
94-
32+
- name: Verify Go version
33+
run: go version || exit 1
34+
- name: List modules
35+
if: file_exists("go.mod")
36+
run: go list -m all || exit 1
9537
post:
96-
- name: "Setup complete"
97-
print: |
98-
✅ Development environment setup complete!
99-
100-
You can now build the project with:
101-
• make build
102-
• go build -o initiat .
103-
104-
Or run tests with:
105-
• make test
106-
• go test ./...
107-
108-
For more commands, see the Makefile or run 'make help'
38+
- name: Go setup complete
39+
print: |
40+
Go setup complete. Build with: go build .

README.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,13 @@ Define your development environment in `.initiat/setup.yml`. Same file works on
3636

3737
**Learn more:** [Setup Scripts](docs/SETUP_SCRIPTS.md)
3838

39-
### Validation and Planning
39+
### Generate and Run Setup
4040

41+
- `initiat setup generate` — detect language/framework in the repo and write `.initiat/setup.yml` and `.initiat/config.yml` from templates (use `--force` to overwrite)
42+
- `initiat setup run` — run `.initiat/setup.yml` offline (no project context or server required)
4143
- `initiat setup validate` — validate `.initiat/setup.yml` against the schema
4244
- `initiat setup schema` — output JSON Schema for autocomplete and tooling
4345

44-
### Onboarding Docs from Repo
45-
46-
Generate onboarding runbooks and README fragments from `.initiat/docs.yml` (or `.initiat/onboard.yml`). Keep "time-to-first-commit" steps in version control next to the setup that makes them possible.
47-
4846
### No Server Required
4947

5048
Core workflows need no account, no device registration, and no network. Clone the repo, run the CLI, and you're done.
@@ -58,24 +56,21 @@ Install the Initiat CLI (e.g. from [releases](https://github.com/InitiatDev/init
5856
### Basic Usage (No Login)
5957

6058
```bash
61-
# Scaffold .initiat/ in the current repo
62-
initiat init
59+
# Generate .initiat/setup.yml and .initiat/config.yml from detected project (Go, Node, Python, Phoenix, Rails, Docker)
60+
initiat setup generate
6361

6462
# Validate setup config
6563
initiat setup validate
6664

67-
# Run the setup script from .initiat/setup.yml
65+
# Run the setup script from .initiat/setup.yml (offline)
6866
initiat setup run
69-
70-
# Generate onboarding docs from .initiat/docs.yml
71-
initiat docs generate
7267
```
7368

7469
All of the above work with no account and no connection to any Initiat server.
7570

7671
## Optional: Cloud Add-ons
7772

78-
If your team wants hosted secret storage, device approval, or project management, those features are available as **opt-in** add-ons. They are not required for setup, validation, or docs.
73+
If your team wants hosted secret storage, device approval, or project management, those features are available as **opt-in** add-ons. They are not required for setup or validation.
7974

8075
- **Hosted secrets (optional)**: Zero-knowledge secret storage with device approval. See [Security & Cloud](docs/SECURITY.md#optional-cloud-add-on).
8176
- **Team & project management (optional)**: Organize access by project; approve devices. See [Command Reference](docs/COMMANDS.md) under "Cloud commands (optional)".
@@ -86,7 +81,7 @@ You can use Initiat entirely without ever creating an account or sending secrets
8681

8782
### Core (Offline-First)
8883

89-
- **[In-Repo YAML](docs/IN_REPO_YAML.md)**: Directory layout, `config.yml`, `setup.yml`, `docs.yml`, and provider-agnostic `env_from`
84+
- **[In-Repo YAML](docs/IN_REPO_YAML.md)**: Directory layout, `config.yml`, `setup.yml`, and provider-agnostic `env_from`
9085
- **[Setup Scripts](docs/SETUP_SCRIPTS.md)**: Complete guide to `.initiat/setup.yml`
9186
- **[Command Reference](docs/COMMANDS.md)**: All CLI commands (offline-first commands first)
9287
- **[Security](docs/SECURITY.md)**: Offline-first security model and optional cloud add-on
@@ -113,12 +108,13 @@ git clone https://github.com/yourusername/initiat-cli.git
113108
cd initiat-cli
114109

115110
go mod tidy
116-
initiat setup run
111+
initiat setup generate # optional: generate .initiat/ from detected stack
112+
initiat setup run # run setup from .initiat/setup.yml
117113
make ci
118114
go build -o initiat .
119115
```
120116

121-
See [Setup Scripts](docs/SETUP_SCRIPTS.md) for the full development environment definition in `.initiat/setup.yml`.
117+
See [Setup Scripts](docs/SETUP_SCRIPTS.md) for the full development environment definition in `.initiat/setup.yml` and [Command Reference](docs/COMMANDS.md) for `initiat setup generate` and `initiat setup run`.
122118

123119
## License
124120

cmd/project.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func checkProjectInitStatus(project *types.Project) bool {
244244
}
245245

246246
func runProjectSetup(cmd *cobra.Command, args []string) error {
247-
setupFile := ".initiat/setup.yml"
247+
setupFile := defaultSetupFile
248248

249249
projectCtx, err := GetProjectContext()
250250
if err != nil {

cmd/setup.go

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
package cmd
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67

78
"github.com/spf13/cobra"
89

10+
"github.com/InitiatDev/initiat-cli/internal/scaffold"
911
"github.com/InitiatDev/initiat-cli/internal/setup"
1012
)
1113

14+
const defaultSetupFile = ".initiat/setup.yml"
15+
1216
var setupCmd = &cobra.Command{
1317
Use: "setup",
1418
Short: "Manage and validate setup scripts",
15-
Long: `Validate setup scripts and generate JSON schemas for .initiat/setup.yml files.`,
19+
Long: `Validate setup scripts, generate .initiat/setup.yml from templates, and run setup.`,
20+
}
21+
22+
var setupGenerateCmd = &cobra.Command{
23+
Use: "generate",
24+
Short: "Generate .initiat/setup.yml from detected project",
25+
Long: `Detect language/framework from the current directory and write .initiat/setup.yml
26+
and .initiat/config.yml. Use --force to overwrite existing setup.yml.`,
27+
RunE: runSetupGenerate,
28+
}
29+
30+
var setupRunCmd = &cobra.Command{
31+
Use: "run",
32+
Short: "Run the setup script",
33+
Long: `Run .initiat/setup.yml (offline; no project context required).
34+
Fails if the script requires secrets.`,
35+
RunE: runSetupRun,
1636
}
1737

38+
var setupGenerateForce bool
39+
1840
var setupValidateCmd = &cobra.Command{
1941
Use: "validate [setup-file]",
2042
Short: "Validate a setup script",
@@ -44,14 +66,84 @@ var schemaOutput string
4466

4567
func init() {
4668
rootCmd.AddCommand(setupCmd)
69+
setupCmd.AddCommand(setupGenerateCmd)
70+
setupCmd.AddCommand(setupRunCmd)
4771
setupCmd.AddCommand(setupValidateCmd)
4872
setupCmd.AddCommand(setupSchemaCmd)
4973

74+
setupGenerateCmd.Flags().BoolVarP(&setupGenerateForce, "force", "f", false, "Overwrite existing .initiat/setup.yml")
5075
setupSchemaCmd.Flags().StringVarP(&schemaOutput, "output", "o", "", "Save schema to file instead of stdout")
5176
}
5277

78+
func runSetupGenerate(cmd *cobra.Command, args []string) error {
79+
dir, _ := os.Getwd()
80+
ids, err := scaffold.Detect(dir)
81+
if err != nil {
82+
return fmt.Errorf("detect: %w", err)
83+
}
84+
projectName := scaffold.ProjectName(dir)
85+
config, err := scaffold.Merge(dir, ids, projectName)
86+
if err != nil {
87+
return fmt.Errorf("merge: %w", err)
88+
}
89+
if err := setup.Validate(config); err != nil {
90+
return fmt.Errorf("generated config invalid: %w", err)
91+
}
92+
wroteSetup, wroteConfig, err := scaffold.Write(config, scaffold.WriteOptions{
93+
Dir: dir,
94+
ProjectName: projectName,
95+
ForceSetup: setupGenerateForce,
96+
ForceConfig: setupGenerateForce,
97+
})
98+
if err != nil {
99+
return fmt.Errorf("write: %w", err)
100+
}
101+
if !wroteSetup && !wroteConfig {
102+
fmt.Println(".initiat/setup.yml already exists. Use --force to overwrite.")
103+
return nil
104+
}
105+
if wroteSetup {
106+
fmt.Println("Wrote .initiat/setup.yml")
107+
}
108+
if wroteConfig {
109+
fmt.Println("Wrote .initiat/config.yml")
110+
}
111+
fmt.Println("Run: initiat setup validate && initiat setup run")
112+
return nil
113+
}
114+
115+
func runSetupRun(cmd *cobra.Command, args []string) error {
116+
setupPath := defaultSetupFile
117+
if len(args) > 0 {
118+
setupPath = args[0]
119+
}
120+
config, err := setup.ParseFile(setupPath)
121+
if err != nil {
122+
return fmt.Errorf("parse %s: %w", setupPath, err)
123+
}
124+
if err := setup.Validate(config); err != nil {
125+
if validationErrs, ok := err.(setup.ValidationErrors); ok {
126+
for _, e := range validationErrs {
127+
fmt.Printf(" - %s\n", e.Error())
128+
}
129+
} else {
130+
fmt.Printf(" - %s\n", err)
131+
}
132+
return fmt.Errorf("validation failed")
133+
}
134+
runner := setup.NewSetupRunner(nil)
135+
if err := runner.Run(config); err != nil {
136+
if errors.Is(err, setup.ErrNoCommandsToExecute) {
137+
fmt.Println("No commands to execute (all steps skipped by conditions).")
138+
return nil
139+
}
140+
return fmt.Errorf("run: %w", err)
141+
}
142+
return nil
143+
}
144+
53145
func runSetupValidate(cmd *cobra.Command, args []string) error {
54-
setupFile := ".initiat/setup.yml"
146+
setupFile := defaultSetupFile
55147
if len(args) > 0 {
56148
setupFile = args[0]
57149
}

0 commit comments

Comments
 (0)