fix(install): allow --wrappers in existing town without recreating HQ (#366)
When running `gt install --wrappers` in an existing Gas Town HQ, the command now installs wrappers directly without requiring --force or recreating the entire HQ structure. Previously, `gt install --wrappers` would fail with "directory is already a Gas Town HQ" unless --force was used, which would then unnecessarily reinitialize the entire workspace. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,14 @@ func runInstall(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Check if already a workspace
|
||||
if isWS, _ := workspace.IsWorkspace(absPath); isWS && !installForce {
|
||||
// If only --wrappers is requested in existing town, just install wrappers and exit
|
||||
if installWrappers {
|
||||
if err := wrappers.Install(); err != nil {
|
||||
return fmt.Errorf("installing wrapper scripts: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ Installed gt-codex and gt-opencode to %s\n", wrappers.BinDir())
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("directory is already a Gas Town HQ (use --force to reinitialize)")
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,61 @@ func TestInstallFormulasProvisioned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestInstallWrappersInExistingTown validates that --wrappers works in an
|
||||
// existing town without requiring --force or recreating HQ structure.
|
||||
func TestInstallWrappersInExistingTown(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
hqPath := filepath.Join(tmpDir, "test-hq")
|
||||
binDir := filepath.Join(tmpDir, "bin")
|
||||
|
||||
// Create bin directory for wrappers
|
||||
if err := os.MkdirAll(binDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create bin dir: %v", err)
|
||||
}
|
||||
|
||||
gtBinary := buildGT(t)
|
||||
|
||||
// First: create HQ without wrappers
|
||||
cmd := exec.Command(gtBinary, "install", hqPath, "--no-beads")
|
||||
cmd.Env = append(os.Environ(), "HOME="+tmpDir)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("first install failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Verify town.json exists (proves HQ was created)
|
||||
townPath := filepath.Join(hqPath, "mayor", "town.json")
|
||||
assertFileExists(t, townPath, "mayor/town.json")
|
||||
|
||||
// Get modification time of town.json before wrapper install
|
||||
townInfo, err := os.Stat(townPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to stat town.json: %v", err)
|
||||
}
|
||||
townModBefore := townInfo.ModTime()
|
||||
|
||||
// Second: install --wrappers in same directory (should not recreate HQ)
|
||||
cmd = exec.Command(gtBinary, "install", hqPath, "--wrappers")
|
||||
cmd.Env = append(os.Environ(), "HOME="+tmpDir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("install --wrappers in existing town failed: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Verify town.json was NOT modified (HQ was not recreated)
|
||||
townInfo, err = os.Stat(townPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to stat town.json after wrapper install: %v", err)
|
||||
}
|
||||
if townInfo.ModTime() != townModBefore {
|
||||
t.Errorf("town.json was modified during --wrappers install, HQ should not be recreated")
|
||||
}
|
||||
|
||||
// Verify output mentions wrapper installation
|
||||
if !strings.Contains(string(output), "gt-codex") && !strings.Contains(string(output), "gt-opencode") {
|
||||
t.Errorf("expected output to mention wrappers, got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInstallNoBeadsFlag validates that --no-beads skips beads initialization.
|
||||
func TestInstallNoBeadsFlag(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
Reference in New Issue
Block a user