Skip to content

Commit db8b1e5

Browse files
committed
refactor(installer): migrate RunFromSnapshot to Plan/Apply; remove unused step* functions
Add PlanFromSnapshot() to build an InstallPlan from snapshot state without interactive prompts, respecting opts flags (Shell/Dotfiles/Macos skip). Add SkipGit field to InstallPlan so Apply() skips git when snapshot has none. Rewrite RunFromSnapshot to call PlanFromSnapshot + Apply + cfg.ApplyState. Delete stepInstallPackages, stepInstallNpm, stepInstallNpmWithRetry, stepRestoreGit, and stepRestoreMacOS which were only called by RunFromSnapshot. Also fix pre-existing compile error in ui/macos_selector.go (pointer receiver) and unused import in search_test.go.
1 parent 1cba595 commit db8b1e5

7 files changed

Lines changed: 69 additions & 348 deletions

File tree

internal/installer/installer.go

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func runInstall(opts *config.InstallOptions, st *config.InstallState) error {
8888
// Apply executes a resolved InstallPlan, reporting progress via r.
8989
// All user interaction has already happened in Plan(); this function only performs actions.
9090
func Apply(plan InstallPlan, r Reporter) error {
91-
if !plan.PackagesOnly {
91+
if !plan.PackagesOnly && !plan.SkipGit {
9292
if err := applyGitConfig(plan, r); err != nil {
9393
return err
9494
}
@@ -228,60 +228,10 @@ func RunFromSnapshot(cfg *config.Config) error {
228228
fmt.Println()
229229
}
230230

231-
if len(st.SnapshotTaps) > 0 {
232-
ui.Info(fmt.Sprintf("Adding %d taps...", len(st.SnapshotTaps)))
233-
fmt.Println()
234-
if err := brew.InstallTaps(st.SnapshotTaps, opts.DryRun); err != nil {
235-
ui.Warn(fmt.Sprintf("Some taps failed: %v", err))
236-
}
237-
fmt.Println()
238-
}
239-
240-
if err := stepInstallPackages(opts, st); err != nil {
241-
cfg.ApplyState(st)
242-
return err
243-
}
244-
245-
if err := stepInstallNpmWithRetry(opts, st); err != nil {
246-
ui.Error(fmt.Sprintf("npm package installation failed: %v", err))
247-
}
248-
249-
var softErrs []error
250-
251-
if st.SnapshotGit != nil {
252-
if err := stepRestoreGit(opts, st); err != nil {
253-
ui.Error(fmt.Sprintf("Git restore failed: %v", err))
254-
softErrs = append(softErrs, fmt.Errorf("git restore: %w", err))
255-
}
256-
}
257-
258-
if err := stepShell(opts, st); err != nil {
259-
ui.Error(fmt.Sprintf("Shell setup failed: %v", err))
260-
softErrs = append(softErrs, fmt.Errorf("shell: %w", err))
261-
}
262-
263-
if err := stepRestoreMacOS(opts, st); err != nil {
264-
ui.Error(fmt.Sprintf("macOS restore failed: %v", err))
265-
softErrs = append(softErrs, fmt.Errorf("macos: %w", err))
266-
}
267-
268-
if st.SnapshotDotfiles != "" {
269-
if err := stepDotfiles(opts, st); err != nil {
270-
ui.Error(fmt.Sprintf("Dotfiles restore failed: %v", err))
271-
softErrs = append(softErrs, fmt.Errorf("dotfiles: %w", err))
272-
}
273-
}
274-
275-
showCompletion(opts, st)
276-
231+
plan := PlanFromSnapshot(opts, st)
232+
err := Apply(plan, ConsoleReporter{})
277233
cfg.ApplyState(st)
278-
279-
if len(softErrs) > 0 {
280-
fmt.Println()
281-
ui.Warn(fmt.Sprintf("%d restore step(s) had errors — check the output above for details.", len(softErrs)))
282-
return errors.Join(softErrs...)
283-
}
284-
return nil
234+
return err
285235
}
286236

287237
func runUpdate(opts *config.InstallOptions, st *config.InstallState) error {
@@ -293,7 +243,7 @@ func runUpdate(opts *config.InstallOptions, st *config.InstallState) error {
293243
}
294244

295245
if !opts.DryRun {
296-
brew.Cleanup()
246+
brew.Cleanup() //nolint:errcheck // best-effort cleanup; failure is non-critical
297247
}
298248

299249
fmt.Println()

internal/installer/plan.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type InstallPlan struct {
2323
// Git
2424
GitName string
2525
GitEmail string
26+
SkipGit bool // when true, Apply skips git configuration entirely
2627

2728
// Packages (fully resolved and categorized)
2829
Formulae []string
@@ -246,7 +247,7 @@ func planDotfilesDecision(opts *config.InstallOptions) (string, error) {
246247
return url, nil
247248
}
248249

249-
if !opts.Silent && !(opts.DryRun && !system.HasTTY()) {
250+
if !opts.Silent && (!opts.DryRun || system.HasTTY()) {
250251
setup, err := ui.Confirm("Do you have your own dotfiles repository?", false)
251252
if err != nil {
252253
return "", err
@@ -285,3 +286,62 @@ func planMacOSDecision(opts *config.InstallOptions) ([]macos.Preference, error)
285286
}
286287
return selected, nil
287288
}
289+
290+
// PlanFromSnapshot builds an InstallPlan from snapshot state without any interactive
291+
// prompts. All decisions are derived from st.Snapshot* fields and opts.
292+
func PlanFromSnapshot(opts *config.InstallOptions, st *config.InstallState) InstallPlan {
293+
plan := InstallPlan{
294+
Version: opts.Version,
295+
DryRun: opts.DryRun,
296+
Silent: opts.Silent,
297+
PackagesOnly: opts.PackagesOnly,
298+
AllowPostInstall: opts.AllowPostInstall,
299+
Taps: st.SnapshotTaps,
300+
SelectedPkgs: st.SelectedPkgs,
301+
}
302+
303+
// Categorize selected packages into formulae, casks, and npm.
304+
cats := categorizeSelectedPackages(opts, st)
305+
plan.Formulae = cats.cli
306+
plan.Casks = cats.cask
307+
plan.Npm = cats.npm
308+
309+
// Git: restore from snapshot when present; skip entirely when snapshot has no git config.
310+
if st.SnapshotGit != nil {
311+
plan.GitName = st.SnapshotGit.UserName
312+
plan.GitEmail = st.SnapshotGit.UserEmail
313+
} else {
314+
plan.SkipGit = true
315+
}
316+
317+
// Dotfiles: non-empty snapshot URL means apply, unless explicitly skipped via flag.
318+
if opts.Dotfiles != "skip" {
319+
plan.DotfilesURL = st.SnapshotDotfiles
320+
}
321+
322+
// Shell: attempt Oh-My-Zsh for snapshot restores unless explicitly skipped.
323+
if opts.Shell != "skip" {
324+
plan.InstallOhMyZsh = true
325+
}
326+
327+
// macOS: convert snapshot preferences to macos.Preference values, unless skipped via flag.
328+
if opts.Macos != "skip" && len(st.SnapshotMacOS) > 0 {
329+
prefs := make([]macos.Preference, 0, len(st.SnapshotMacOS))
330+
for _, p := range st.SnapshotMacOS {
331+
prefType := p.Type
332+
if prefType == "" {
333+
prefType = macos.InferPreferenceType(p.Value)
334+
}
335+
prefs = append(prefs, macos.Preference{
336+
Domain: p.Domain,
337+
Key: p.Key,
338+
Type: prefType,
339+
Value: p.Value,
340+
Desc: p.Desc,
341+
})
342+
}
343+
plan.MacOSPrefs = prefs
344+
}
345+
346+
return plan
347+
}

internal/installer/step_git.go

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -91,58 +91,3 @@ func stepGitConfig(opts *config.InstallOptions, st *config.InstallState) error {
9191
return nil
9292
}
9393

94-
func stepRestoreGit(opts *config.InstallOptions, st *config.InstallState) error {
95-
ui.Header("Restore: Git Configuration")
96-
fmt.Println()
97-
98-
git := st.SnapshotGit
99-
if git.UserName == "" && git.UserEmail == "" {
100-
ui.Muted("No git config in snapshot, skipping")
101-
fmt.Println()
102-
return nil
103-
}
104-
105-
existingName, existingEmail := system.GetExistingGitConfig()
106-
107-
if existingName != "" && existingEmail != "" {
108-
ui.Success(fmt.Sprintf("✓ Already configured: %s <%s>", existingName, existingEmail))
109-
fmt.Println()
110-
return nil
111-
}
112-
113-
if opts.DryRun {
114-
if existingName == "" && git.UserName != "" {
115-
fmt.Printf("[DRY-RUN] Would set git user.name = %s\n", git.UserName)
116-
}
117-
if existingEmail == "" && git.UserEmail != "" {
118-
fmt.Printf("[DRY-RUN] Would set git user.email = %s\n", git.UserEmail)
119-
}
120-
fmt.Println()
121-
return nil
122-
}
123-
124-
nameToSet := existingName
125-
emailToSet := existingEmail
126-
if existingName == "" && git.UserName != "" {
127-
nameToSet = git.UserName
128-
}
129-
if existingEmail == "" && git.UserEmail != "" {
130-
emailToSet = git.UserEmail
131-
}
132-
133-
if nameToSet == "" || emailToSet == "" {
134-
ui.Warn("Incomplete git config in snapshot, skipping (need both name and email)")
135-
fmt.Println()
136-
return nil
137-
}
138-
139-
if nameToSet != existingName || emailToSet != existingEmail {
140-
if err := system.ConfigureGit(nameToSet, emailToSet); err != nil {
141-
return fmt.Errorf("restore git config: %w", err)
142-
}
143-
}
144-
145-
ui.Success(fmt.Sprintf("Git restored: %s <%s>", nameToSet, emailToSet))
146-
fmt.Println()
147-
return nil
148-
}

0 commit comments

Comments
 (0)