When upgrading gt on an existing installation without .installed.json, formulas that exist but don't match embedded were incorrectly marked as "modified" (implying user customization). Now they're marked "untracked" and are safe to update since there's no record of user modification. This improves the upgrade experience: - "modified" = tracked file user changed (skip update) - "untracked" = file exists but not tracked (safe to update) Adds 3 new tests for untracked scenarios. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
package doctor
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/steveyegge/gastown/internal/formula"
|
|
)
|
|
|
|
// FormulaCheck verifies that embedded formulas are up-to-date.
|
|
// It detects outdated formulas (binary updated), missing formulas (user deleted),
|
|
// and modified formulas (user customized). Can auto-fix outdated and missing.
|
|
type FormulaCheck struct {
|
|
FixableCheck
|
|
}
|
|
|
|
// NewFormulaCheck creates a new formula check.
|
|
func NewFormulaCheck() *FormulaCheck {
|
|
return &FormulaCheck{
|
|
FixableCheck: FixableCheck{
|
|
BaseCheck: BaseCheck{
|
|
CheckName: "formulas",
|
|
CheckDescription: "Check embedded formulas are up-to-date",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Run checks if formulas need updating.
|
|
func (c *FormulaCheck) Run(ctx *CheckContext) *CheckResult {
|
|
report, err := formula.CheckFormulaHealth(ctx.TownRoot)
|
|
if err != nil {
|
|
return &CheckResult{
|
|
Name: c.Name(),
|
|
Status: StatusWarning,
|
|
Message: fmt.Sprintf("Could not check formulas: %v", err),
|
|
}
|
|
}
|
|
|
|
// All good
|
|
if report.Outdated == 0 && report.Missing == 0 && report.Modified == 0 && report.New == 0 && report.Untracked == 0 {
|
|
return &CheckResult{
|
|
Name: c.Name(),
|
|
Status: StatusOK,
|
|
Message: fmt.Sprintf("%d formulas up-to-date", report.OK),
|
|
}
|
|
}
|
|
|
|
// Build details
|
|
var details []string
|
|
var needsFix bool
|
|
|
|
for _, f := range report.Formulas {
|
|
switch f.Status {
|
|
case "outdated":
|
|
details = append(details, fmt.Sprintf(" %s: update available", f.Name))
|
|
needsFix = true
|
|
case "missing":
|
|
details = append(details, fmt.Sprintf(" %s: missing (will reinstall)", f.Name))
|
|
needsFix = true
|
|
case "modified":
|
|
details = append(details, fmt.Sprintf(" %s: locally modified (skipping)", f.Name))
|
|
case "new":
|
|
details = append(details, fmt.Sprintf(" %s: new formula available", f.Name))
|
|
needsFix = true
|
|
case "untracked":
|
|
details = append(details, fmt.Sprintf(" %s: untracked (will update)", f.Name))
|
|
needsFix = true
|
|
}
|
|
}
|
|
|
|
// Determine status
|
|
status := StatusOK
|
|
if needsFix {
|
|
status = StatusWarning
|
|
}
|
|
|
|
// Build message
|
|
var parts []string
|
|
if report.Outdated > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d outdated", report.Outdated))
|
|
}
|
|
if report.Missing > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d missing", report.Missing))
|
|
}
|
|
if report.New > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d new", report.New))
|
|
}
|
|
if report.Untracked > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d untracked", report.Untracked))
|
|
}
|
|
if report.Modified > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d modified", report.Modified))
|
|
}
|
|
|
|
message := fmt.Sprintf("Formulas: %s", strings.Join(parts, ", "))
|
|
|
|
result := &CheckResult{
|
|
Name: c.Name(),
|
|
Status: status,
|
|
Message: message,
|
|
Details: details,
|
|
}
|
|
|
|
if needsFix {
|
|
result.FixHint = "Run 'gt doctor --fix' to update formulas"
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Fix updates outdated and missing formulas.
|
|
func (c *FormulaCheck) Fix(ctx *CheckContext) error {
|
|
updated, skipped, reinstalled, err := formula.UpdateFormulas(ctx.TownRoot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Log what was done (caller will re-run check to show new status)
|
|
if updated > 0 || reinstalled > 0 || skipped > 0 {
|
|
// The doctor framework will re-run the check after fix
|
|
// so we don't need to log here
|
|
_ = updated
|
|
_ = reinstalled
|
|
_ = skipped
|
|
}
|
|
|
|
return nil
|
|
}
|