Skip to content

Commit 4df5394

Browse files
authored
refactor(cli): make openboot install the canonical entry point (#52)
* refactor(cli): make openboot install the canonical entry point Root command no longer doubles as install — bare `openboot` now shows help instead of running the install wizard. All install flags move to `openboot install`, which is now the single, explicit entry point. - Remove RunE and all install flags from root command - Add missing --post-install flag to installCmd (was root-only before) - Update install.sh to exec `openboot install` instead of bare `openboot` (snapshot mode still execs `openboot snapshot` via passthrough) - Simplify PersistentPreRunE: map lookup → single cmd.Name() == "install" check Breaking: `openboot -p developer` style shortcuts no longer work; use `openboot install -p developer` instead. * fix: update all callers to use openboot install subcommand Fixes CI failures from the root-command refactor. The mock server's inline install script and all e2e test exec.Command calls were still using the old root-level flag syntax. - scripts/mock-server.py: generated install script now calls `openboot install -s -u` instead of bare `openboot -s -u` - test/e2e/smoke_test.go: dry-run exec call updated - test/e2e/real_install_test.go: all exec calls updated (L6 tier) - test/e2e/macos_defaults_e2e_test.go: doc comment updated - test/e2e/dotfiles_e2e_test.go: doc comments updated - README.md, .github/pull_request_template.md: examples updated - internal/cli/install.go: remove stale comment about root alias
1 parent 82804b6 commit 4df5394

10 files changed

Lines changed: 43 additions & 64 deletions

File tree

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<!-- How did you test this? -->
1313
- [ ] `go vet ./...` passes
1414
- [ ] Relevant tests added or updated
15-
- [ ] Tested locally (`./openboot --dry-run` or similar)
15+
- [ ] Tested locally (`./openboot install --dry-run` or similar)
1616

1717
## Notes for reviewer
1818

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ Bug reports and feature requests: [open an issue](https://github.com/openbootdot
236236
git clone https://github.com/openbootdotdev/openboot.git
237237
cd openboot
238238
go build -o openboot ./cmd/openboot
239-
./openboot --dry-run
239+
./openboot install --dry-run
240240
```
241241

242242
</details>

internal/cli/install.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,40 @@ import (
1717
"github.com/openbootdotdev/openboot/internal/ui"
1818
)
1919

20-
// installCfg is the single config instance shared by the root command (openboot)
21-
// and the install subcommand (openboot install). Both bind their flags here so
22-
// that `openboot -p developer` and `openboot install -p developer` are identical.
20+
// installCfg is the config instance used by the install subcommand.
2321
var installCfg = &config.Config{}
2422

2523
var installCmd = &cobra.Command{
2624
Use: "install [source]",
2725
Short: "Set up your Mac dev environment",
2826
Long: `Install and configure your Mac development environment.
2927
30-
Source resolution (position argument, in order):
28+
Source resolution (positional argument, in order):
3129
1. ./path, /path, or *.json → local file
3230
2. user/slug → openboot.dev config
3331
3. preset name → built-in preset (minimal, developer, full)
3432
4. other word → treated as an openboot.dev alias
3533
36-
With no arguments, resumes from your saved sync source (or interactive if none).
34+
With no arguments, resumes from your saved sync source (or runs the interactive
35+
wizard if you have never synced before).
3736
38-
Explicit flags (--from, --user, -p) override the positional argument.`,
39-
Example: ` # Resume last sync (or interactive if never synced)
37+
Explicit flags (--from, --user, -p) take precedence over the positional argument.`,
38+
Example: ` # Interactive setup (or resume last sync)
4039
openboot install
4140
42-
# Install from a cloud config
43-
openboot install alice/dev-setup
41+
# Quick setup with a built-in preset
42+
openboot install -p developer
4443
45-
# Install from a local file
46-
openboot install ./backup.json
44+
# Install from your cloud config
45+
openboot install -u githubusername
4746
48-
# Install a built-in preset
49-
openboot install -p developer
47+
# Install from a specific cloud config
48+
openboot install alice/dev-setup
49+
50+
# Install from a local file or snapshot
51+
openboot install --from ./backup.json
5052
51-
# Preview without installing
53+
# Preview changes without installing
5254
openboot install --dry-run`,
5355
Args: cobra.MaximumNArgs(1),
5456
SilenceUsage: true,
@@ -68,6 +70,7 @@ func init() {
6870
installCmd.Flags().StringVar(&installCfg.Shell, "shell", "", "shell setup: install, skip")
6971
installCmd.Flags().StringVar(&installCfg.Macos, "macos", "", "macOS preferences: configure, skip")
7072
installCmd.Flags().StringVar(&installCfg.Dotfiles, "dotfiles", "", "dotfiles: clone, link, skip")
73+
installCmd.Flags().StringVar(&installCfg.PostInstall, "post-install", "", "post-install script: skip")
7174

7275
installCmd.Flags().BoolVar(&installCfg.Update, "update", false, "update Homebrew before installing")
7376
installCmd.Flags().BoolVar(&installCfg.AllowPostInstall, "allow-post-install", false, "allow post-install scripts in silent mode")

internal/cli/root.go

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,26 @@ var version = "dev"
1818

1919
var rootCmd = &cobra.Command{
2020
Use: "openboot",
21-
Short: "Set up your Mac dev environment in one command",
22-
Long: `OpenBoot - Mac development environment setup tool
21+
Short: "Set up your Mac dev environment",
22+
Long: `OpenBoot Mac development environment setup tool
2323
2424
Automates installation of Homebrew packages, CLI tools, GUI apps, npm packages,
2525
shell configuration, and macOS preferences.`,
26-
Example: ` # Interactive setup with package selection
27-
openboot
26+
Example: ` # Interactive setup
27+
openboot install
2828
2929
# Quick setup with a preset
30-
openboot -p developer
30+
openboot install -p developer
3131
3232
# Install from your cloud config
33-
openboot -u githubusername
33+
openboot install -u githubusername
3434
3535
# Install from a local config or snapshot file
36-
openboot --from config.json
36+
openboot install --from config.json
3737
3838
# Capture your current environment
3939
openboot snapshot --json > my-setup.json`,
40+
SilenceUsage: true,
4041
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
4142
// Install always-on file logging; --verbose controls stderr level.
4243
// Failure here is never fatal — Init falls back to stderr internally.
@@ -52,46 +53,17 @@ shell configuration, and macOS preferences.`,
5253
// Only the install flow needs the package catalog and auto-update.
5354
// All other commands (snapshot, login, logout, etc.) run without
5455
// network overhead.
55-
installCmds := map[string]bool{
56-
"openboot": true, // root command delegates to install
57-
"install": true,
58-
}
59-
if installCmds[cmd.Name()] {
56+
if cmd.Name() == "install" {
6057
updater.AutoUpgrade(version)
6158
config.RefreshPackagesFromRemote()
6259
}
6360

6461
return nil
6562
},
66-
RunE: func(cmd *cobra.Command, args []string) error {
67-
// `openboot` with no subcommand is equivalent to `openboot install`.
68-
// Root flags bind directly to installCfg, so no bridging is needed.
69-
return runInstallCmd(cmd, args)
70-
},
7163
}
7264

7365
func init() {
7466
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "enable debug logging to stderr")
75-
rootCmd.Flags().SortFlags = false
76-
77-
// Root is an alias for `openboot install`, so its flags bind directly to
78-
// installCfg — the same struct used by the install subcommand. This ensures
79-
// `openboot -p developer` and `openboot install -p developer` are identical
80-
// code paths with no config divergence.
81-
rootCmd.Flags().StringVarP(&installCfg.Preset, "preset", "p", "", "use a preset: minimal, developer, full")
82-
rootCmd.Flags().StringVarP(&installCfg.User, "user", "u", "", "install from openboot.dev/username config")
83-
rootCmd.Flags().String("from", "", "install from a local config or snapshot JSON file")
84-
rootCmd.Flags().BoolVarP(&installCfg.Silent, "silent", "s", false, "non-interactive mode (for CI/CD)")
85-
rootCmd.Flags().BoolVar(&installCfg.DryRun, "dry-run", false, "preview changes without installing")
86-
rootCmd.Flags().BoolVar(&installCfg.PackagesOnly, "packages-only", false, "install packages only, skip system config")
87-
88-
rootCmd.Flags().StringVar(&installCfg.Shell, "shell", "", "shell setup: install, skip")
89-
rootCmd.Flags().StringVar(&installCfg.Macos, "macos", "", "macOS preferences: configure, skip")
90-
rootCmd.Flags().StringVar(&installCfg.Dotfiles, "dotfiles", "", "dotfiles: clone, link, skip")
91-
rootCmd.Flags().StringVar(&installCfg.PostInstall, "post-install", "", "post-install script: skip")
92-
rootCmd.Flags().BoolVar(&installCfg.AllowPostInstall, "allow-post-install", false, "allow post-install scripts in silent mode")
93-
94-
rootCmd.Flags().BoolVar(&installCfg.Update, "update", false, "update Homebrew before installing")
9567

9668
rootCmd.AddCommand(installCmd)
9769
rootCmd.AddCommand(versionCmd)

scripts/install.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,11 @@ main() {
188188
echo "Starting OpenBoot setup..."
189189
echo ""
190190

191-
exec openboot "$@"
191+
if [[ "$snapshot_mode" == true ]]; then
192+
exec openboot "$@"
193+
else
194+
exec openboot install "$@"
195+
fi
192196
}
193197

194198
main "$@"

scripts/mock-server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
fi
5454
export OPENBOOT_DRY_RUN=true
5555
export OPENBOOT_API_URL=http://localhost:{port}
56-
{binary} -s -u testuser/test-config
56+
{binary} install -s -u testuser/test-config
5757
exit 0
5858
}}
5959
main

test/e2e/dotfiles_e2e_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222

2323
// TestVM_Journey_DotfilesClonedAndLinked runs
2424
//
25-
// openboot --preset minimal --silent --dotfiles clone --shell skip --macos skip
25+
// openboot install --preset minimal --silent --dotfiles clone --shell skip --macos skip
2626
//
2727
// and verifies that:
2828
// 1. ~/.dotfiles is a valid git repository (clone succeeded).
@@ -112,7 +112,7 @@ func TestVM_Journey_DotfilesClonedAndLinked(t *testing.T) {
112112

113113
// TestVM_Journey_DotfilesLink_OnlyLinks runs
114114
//
115-
// openboot --preset minimal --silent --dotfiles link --shell skip --macos skip
115+
// openboot install --preset minimal --silent --dotfiles link --shell skip --macos skip
116116
//
117117
// when ~/.dotfiles already exists (from a previous clone), verifying that the
118118
// link-only mode does not re-clone but still creates symlinks.

test/e2e/macos_defaults_e2e_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type macOSPrefCheck struct {
3131

3232
// TestVM_Journey_MacOSDefaults_AllCategoriesWritten runs
3333
//
34-
// openboot --preset minimal --silent --shell skip --dotfiles skip --macos configure
34+
// openboot install --preset minimal --silent --shell skip --dotfiles skip --macos configure
3535
//
3636
// and verifies that representative preferences from each of the eight
3737
// categories in internal/macos/categories.go are actually written to the

test/e2e/real_install_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestE2E_InstallSinglePackage_JQ(t *testing.T) {
2525
binary := testutil.BuildTestBinary(t)
2626

2727
// When: we install jq via openboot (minimal preset includes jq)
28-
cmd := exec.Command(binary, "--packages-only", "--silent", "--preset", "minimal")
28+
cmd := exec.Command(binary, "install", "--packages-only", "--silent", "--preset", "minimal")
2929
cmd.Env = append(os.Environ(),
3030
"OPENBOOT_GIT_NAME=Test User",
3131
"OPENBOOT_GIT_EMAIL=test@example.com",
@@ -67,7 +67,7 @@ func TestE2E_InstallMultiplePackages(t *testing.T) {
6767
}`)
6868
defer os.Remove(tmpConfig)
6969

70-
cmd := exec.Command(binary, "--packages-only", "--silent", "--preset", "minimal")
70+
cmd := exec.Command(binary, "install", "--packages-only", "--silent", "--preset", "minimal")
7171
cmd.Env = append(os.Environ(),
7272
"OPENBOOT_GIT_NAME=Test User",
7373
"OPENBOOT_GIT_EMAIL=test@example.com",
@@ -183,7 +183,7 @@ func TestE2E_DryRunDoesNotInstall(t *testing.T) {
183183
}`)
184184
defer os.Remove(tmpConfig)
185185

186-
cmd := exec.Command(binary, "--dry-run", "--packages-only", "--silent", "--preset", "minimal")
186+
cmd := exec.Command(binary, "install", "--dry-run", "--packages-only", "--silent", "--preset", "minimal")
187187
cmd.Env = append(os.Environ(),
188188
"OPENBOOT_GIT_NAME=Test User",
189189
"OPENBOOT_GIT_EMAIL=test@example.com",
@@ -201,7 +201,7 @@ func TestE2E_BrewUpdateBeforeInstall(t *testing.T) {
201201
binary := testutil.BuildTestBinary(t)
202202

203203
// Given: we request brew update
204-
cmd := exec.Command(binary, "--update", "--dry-run", "--packages-only", "--silent", "--preset", "minimal")
204+
cmd := exec.Command(binary, "install", "--update", "--dry-run", "--packages-only", "--silent", "--preset", "minimal")
205205
cmd.Env = append(os.Environ(),
206206
"OPENBOOT_GIT_NAME=Test User",
207207
"OPENBOOT_GIT_EMAIL=test@example.com",
@@ -224,7 +224,7 @@ func TestE2E_GitConfigSetup(t *testing.T) {
224224
testEmail := "e2e-test@example.com"
225225

226226
// Given: we have test git credentials
227-
cmd := exec.Command(binary, "--packages-only", "--silent", "--preset", "minimal")
227+
cmd := exec.Command(binary, "install", "--packages-only", "--silent", "--preset", "minimal")
228228
cmd.Env = append(os.Environ(),
229229
"OPENBOOT_GIT_NAME="+testName,
230230
"OPENBOOT_GIT_EMAIL="+testEmail,

test/e2e/smoke_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func TestSmoke_DryRunNoSideEffects(t *testing.T) {
8080
before := captureSnapshot(t, binary)
8181

8282
// When: run with --dry-run --preset full
83-
cmd := exec.Command(binary, "--preset", "full", "--dry-run", "--silent")
83+
cmd := exec.Command(binary, "install", "--preset", "full", "--dry-run", "--silent")
8484
cmd.Env = append(os.Environ(),
8585
"OPENBOOT_GIT_NAME=Smoke Test",
8686
"OPENBOOT_GIT_EMAIL=smoke@test.local",

0 commit comments

Comments
 (0)