diff --git a/internal/config/data/zsh-plugins.yaml b/internal/config/data/zsh-plugins.yaml index 64671d3..ec13d54 100644 --- a/internal/config/data/zsh-plugins.yaml +++ b/internal/config/data/zsh-plugins.yaml @@ -13,3 +13,7 @@ plugins: repo: https://github.com/zsh-users/zsh-completions - name: zsh-history-substring-search repo: https://github.com/zsh-users/zsh-history-substring-search + - name: fast-syntax-highlighting + repo: https://github.com/zdharma-continuum/fast-syntax-highlighting + - name: zsh-autocomplete + repo: https://github.com/marlonrichert/zsh-autocomplete diff --git a/internal/config/zshplugins_test.go b/internal/config/zshplugins_test.go index 79c62d8..2ee6043 100644 --- a/internal/config/zshplugins_test.go +++ b/internal/config/zshplugins_test.go @@ -13,6 +13,22 @@ func TestZshPluginRepoURL_KnownExternal(t *testing.T) { assert.True(t, strings.HasPrefix(url, "https://"), "repo URL must be https, got %q", url) } +func TestZshPluginRepoURL_PopularExternalPlugins(t *testing.T) { + // Regression: these external plugins were referenced by real user configs + // but missing from the catalog, so cloneExternalPlugins skipped them and + // oh-my-zsh logged "plugin not found" at shell startup. + for _, name := range []string{ + "zsh-autosuggestions", + "zsh-syntax-highlighting", + "fast-syntax-highlighting", + "zsh-autocomplete", + } { + url, ok := ZshPluginRepoURL(name) + assert.True(t, ok, "%s should be a known external plugin", name) + assert.True(t, strings.HasPrefix(url, "https://"), "%s repo must be https, got %q", name, url) + } +} + func TestZshPluginRepoURL_BuiltinReturnsFalse(t *testing.T) { for _, builtin := range []string{"git", "docker", "kubectl", "z"} { url, ok := ZshPluginRepoURL(builtin) diff --git a/internal/installer/plan.go b/internal/installer/plan.go index 49f9e50..8f93583 100644 --- a/internal/installer/plan.go +++ b/internal/installer/plan.go @@ -93,6 +93,12 @@ func planFromRemoteConfig(opts *config.InstallOptions, st *config.InstallState, if rc.Shell != nil && rc.Shell.OhMyZsh { plan.InstallOhMyZsh = true + // Carry theme and plugins through so applyShell takes the restore path + // (writes plugins=() and git-clones external plugins). Dropping these + // silently downgraded remote-config installs to a bare OMZ install with + // no plugins cloned. + plan.ShellTheme = rc.Shell.Theme + plan.ShellPlugins = rc.Shell.Plugins } for _, p := range rc.MacOSPrefs { diff --git a/internal/installer/plan_test.go b/internal/installer/plan_test.go index 298ccd5..4f94efe 100644 --- a/internal/installer/plan_test.go +++ b/internal/installer/plan_test.go @@ -418,6 +418,29 @@ func TestPlanFromSnapshot_ShellRestored(t *testing.T) { assert.Equal(t, []string{"git", "kubectl"}, plan.ShellPlugins) } +func TestPlan_RemoteConfig_ShellThemeAndPluginsRestored(t *testing.T) { + // Regression: planFromRemoteConfig used to set only InstallOhMyZsh and drop + // the theme/plugins, so `openboot install ` installed a bare OMZ and + // never wrote plugins=() or cloned external plugins. + opts := &config.InstallOptions{DryRun: true, Silent: true} + st := &config.InstallState{ + RemoteConfig: &config.RemoteConfig{ + Shell: &config.RemoteShellConfig{ + OhMyZsh: true, + Theme: "robbyrussell", + Plugins: []string{"git", "zsh-autosuggestions", "fast-syntax-highlighting"}, + }, + }, + } + + plan, err := Plan(opts, st) + require.NoError(t, err) + + assert.True(t, plan.InstallOhMyZsh) + assert.Equal(t, "robbyrussell", plan.ShellTheme) + assert.Equal(t, []string{"git", "zsh-autosuggestions", "fast-syntax-highlighting"}, plan.ShellPlugins) +} + func TestPlanFromSnapshot_ShellSkipFlag(t *testing.T) { cfg := &config.Config{ InstallOptions: config.InstallOptions{