diff --git a/AGENTS.md b/AGENTS.md index f9733fe..a8c4541 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,9 @@ The actual config lives under `~/.config/zsh/`. ### Shared files -- `configs/shared/tools.zsh`: external tool initialization, including cached init scripts +- `configs/shared/tools.zsh`: thin orchestrator that sources `shared/tools/_loader.zsh` and calls `_load_tool_config` for each tool module +- `configs/shared/tools/_loader.zsh`: `_load_tool_config()` helper and `_source_cached_tool_init()` with version-based cache invalidation +- `configs/shared/tools/{tool}.zsh`: per-tool module files (fzf, runtime, atuin, bun), loaded on demand by `_load_tool_config` - `configs/shared/prompt.zsh`: prompt/theme loading ### Local files diff --git a/README.md b/README.md index 852fd52..77b472e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Named after Barney's catchphrase from [How I Met Your Mother](https://www.themov - **Verify mode** — check your installation integrity - **Clean mode** — remove suitup config files - `--help` output for quick command discovery -- Backs up existing zsh startup files to `~/.config/suitup/backups/` before changing shell startup config +- Backs up existing zsh startup files to `~/.config/zsh/backups/` before changing shell startup config - Powerlevel10k is optional; recommended because its async git status stays responsive in large repositories - Idempotent — safe to run multiple times - No private/company-specific content — clean, generic configs @@ -111,7 +111,7 @@ Interactive step-by-step setup with selectable steps: 10. **Vim Config** — basic vim setup 11. **Dock Cleanup** — clean macOS Dock -Before suitup updates shell startup config, it backs up existing zsh startup files such as `.zshrc`, `.zprofile`, `.zshenv`, and `.zlogin` to `~/.config/suitup/backups/`. +Before suitup updates shell startup config, it backs up existing zsh startup files such as `.zshrc`, `.zprofile`, `.zshenv`, and `.zlogin` to `~/.config/zsh/backups/`. If you choose Powerlevel10k, suitup keeps prompt loading non-interactive during setup. When `~/.p10k.zsh` is missing, it falls back to a basic prompt until you run `p10k configure` yourself. @@ -162,7 +162,7 @@ This command: - extracts detected PATH-related lines from `.zshrc` - appends them to `~/.config/zsh/core/paths.zsh` -- creates a backup in `~/.config/suitup/backups/` +- creates a backup in `~/.config/zsh/backups/` - runs a post-migration Zsh syntax check - rolls back automatically if validation fails @@ -175,7 +175,7 @@ node src/cli.js clean Attempts a safe uninstall of suitup-managed config: - restores the latest non-suitup backup of `~/.zshrc` / `~/.zshenv` when available -- removes suitup-generated `~/.config/zsh/` and `~/.config/suitup/` files when they still match shipped templates +- removes suitup-generated `~/.config/zsh/` files when they still match shipped templates - strips `# >>> suitup/... >>>` blocks from an existing `~/.zshrc` if you used `append` - preserves user-modified files instead of deleting them blindly @@ -231,7 +231,13 @@ After setup, your shell config looks like: paths.zsh # PATH + tool bootstrap entries options.zsh # Zsh shell options shared/ - tools.zsh # Tool init (fzf, atuin, zoxide, fnm) + tools.zsh # Tool init orchestrator + tools/ + _loader.zsh # _load_tool_config() + version-based cache + fzf.zsh # FZF env, init, Ctrl-T widget + runtime.zsh # zoxide + fnm + atuin.zsh # Atuin history (owns Ctrl-R) + bun.zsh # Bun async completion plugins.zsh # zinit plugin declarations highlighting.zsh # zsh-syntax-highlighting styles aliases.zsh # Shared aliases @@ -239,9 +245,8 @@ After setup, your shell config looks like: prompt.zsh # Prompt/theme (p10k) local/ machine.zsh # Machine-specific overrides + config.vim # Vim config secrets.zsh # API keys (create manually, gitignored) -~/.config/suitup/ - config.vim # Vim config ``` ## Testing diff --git a/configs/core/perf.zsh b/configs/core/perf.zsh index 6427605..fbec040 100644 --- a/configs/core/perf.zsh +++ b/configs/core/perf.zsh @@ -49,9 +49,9 @@ _print_duration_row() { if (( rounded_ms > 1000 )); then local sec=$(( rounded_ms / 1000 )) local rem=$(( rounded_ms % 1000 )) - printf '│ %-8s %12d.%01ds │\n' "$name" "$sec" "$(( rem / 100 ))" + printf '│ %-10s %12d.%01ds │\n' "$name" "$sec" "$(( rem / 100 ))" else - printf '│ %-8s %13dms │\n' "$name" "$rounded_ms" + printf '│ %-10s %13dms │\n' "$name" "$rounded_ms" fi } @@ -66,13 +66,17 @@ _zsh_report() { local i echo '' - echo '┌──────────────────────────┐' + echo '┌────────────────────────────┐' for i in {1..${#_zsh_stage_names}}; do _print_duration_row "${_zsh_stage_names[$i]}" "${_zsh_stage_durations[$i]}" done - echo '├──────────────────────────┤' + echo '├────────────────────────────┤' _print_duration_row 'total' "$total_ms" - echo '└──────────────────────────┘' + echo '└────────────────────────────┘' + + if [[ "${_zsh_completion_cache_mode:-}" == 'cache-hit' && -n "${_zsh_compdump_file:-}" ]]; then + printf 'completion cache hit; remove %s to rebuild\n' "$_zsh_compdump_file" + fi } diff --git a/configs/shared/aliases.zsh b/configs/shared/aliases.zsh index eab7b1f..b40f062 100644 --- a/configs/shared/aliases.zsh +++ b/configs/shared/aliases.zsh @@ -16,7 +16,7 @@ alias cat="bat" # git alias gco="git checkout" alias gph="git push" -alias gphu='local b; b=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); [[ $b != HEAD ]] && gph -u origin "$b"' +alias gphu='local b; b=$(git branch --show-current 2>/dev/null); [[ -n $b ]] && gph -u origin "$b"' alias gcol="git checkout --no-guess" alias gpl="git pull --rebase" alias gcz="git-cz" diff --git a/configs/shared/fzf.zsh b/configs/shared/fzf.zsh deleted file mode 100644 index 5f97a30..0000000 --- a/configs/shared/fzf.zsh +++ /dev/null @@ -1,33 +0,0 @@ -# FZF -# Note: home-directory branch returns nothing (no empty line) to avoid -# cluttering the picker when running fzf from $HOME. -export FZF_DEFAULT_COMMAND=' - if [[ "$PWD" != "$HOME" ]]; then - fd --type d --hidden --follow \ - --base-directory . \ - --exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \ - 2>/dev/null; - fd --type f --hidden --follow \ - --base-directory . \ - --exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \ - 2>/dev/null - fi -' - -export FZF_CTRL_T_COMMAND=$FZF_DEFAULT_COMMAND - -export FZF_CTRL_T_OPTS=" - --height 100% - --header '[C-/] toggle preview | [Alt-j/k] scroll preview' - --preview 'if [ -f {} ]; then - bat --color=always --style=plain --line-range :300 {}; - elif [ -d {} ]; then - eza -L 2 -T --git-ignore {} 2>/dev/null | head -20; - fi' - --preview-window=right:50%:wrap - --bind 'ctrl-/:toggle-preview' - --bind 'alt-j:preview-down' - --bind 'alt-k:preview-up' - --bind 'ctrl-d:preview-page-down' - --bind 'ctrl-u:preview-page-up' -" diff --git a/configs/shared/tools.zsh b/configs/shared/tools.zsh index a5e470b..eb618e3 100644 --- a/configs/shared/tools.zsh +++ b/configs/shared/tools.zsh @@ -1,37 +1,27 @@ # ============================================================================ -# External tool configuration and initialization +# Shared tool entry point # ============================================================================ +# +# This file is the only tool entry point sourced by `~/.zshrc`. +# +# Maintenance rules: +# - Keep this file orchestration-only. Do not add tool-specific logic here. +# - Add shared helper functions to `shared/tools/_loader.zsh`. +# - Add each tool to its own file under `shared/tools/`. +# - Keep the load order explicit. Do not auto-discover files from the directory, +# because keyboard bindings and tool init order matter. +# - If two tools compete for the same binding, document the winner here. -# FZF configuration (env vars for fd-based search and preview bindings) -[[ -f "${ZDOTDIR:-$HOME/.config/zsh}/shared/fzf.zsh" ]] && source "${ZDOTDIR:-$HOME/.config/zsh}/shared/fzf.zsh" +_zsh_tools_dir="${ZSH_CONFIG:-$HOME/.config/zsh}/shared/tools" -_zsh_tools_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/zsh" -[[ -d "$_zsh_tools_cache_dir" ]] || mkdir -p "$_zsh_tools_cache_dir" +source "$_zsh_tools_dir/_loader.zsh" -_source_cached_tool_init() { - local cache_name="$1" - local binary_name="$2" - local init_command="$3" - local binary_path - local cache_file="$_zsh_tools_cache_dir/${cache_name}.zsh" - local tmp_file="${cache_file}.tmp" - - binary_path=$(command -v "$binary_name") || return 0 - - if [[ ! -s "$cache_file" || "$binary_path" -nt "$cache_file" ]]; then - if eval "$init_command" >| "$tmp_file" 2>/dev/null; then - mv "$tmp_file" "$cache_file" - else - rm -f "$tmp_file" - eval "$init_command" - return - fi - fi - - source "$cache_file" -} - -_source_cached_tool_init fzf-init fzf 'fzf --zsh' -_source_cached_tool_init atuin-init atuin 'atuin init zsh' -_source_cached_tool_init zoxide-init zoxide 'zoxide init zsh' -_source_cached_tool_init fnm-init fnm 'fnm env --use-on-cd --version-file-strategy=recursive --shell zsh' +# Load order matters: +# 1. fzf defines Ctrl-T and shared picker behavior. +# 2. runtime tools initialize common navigation/node helpers. +# 3. atuin loads after fzf so it can own Ctrl-R. +# 4. bun completion is deferred because it is non-essential at startup. +_load_tool_config fzf +_load_tool_config runtime +_load_tool_config atuin +_load_tool_config bun diff --git a/configs/shared/tools/_loader.zsh b/configs/shared/tools/_loader.zsh new file mode 100644 index 0000000..0952799 --- /dev/null +++ b/configs/shared/tools/_loader.zsh @@ -0,0 +1,57 @@ +# ============================================================================ +# Shared tool loader and cached init helpers +# ============================================================================ +# +# This file contains common helpers used by multiple tool modules. +# +# Maintenance rules: +# - Put reusable helper functions here only when at least two tool modules need +# them, or when the helper exists purely to support module loading. +# - Keep tool-specific env vars, aliases, bindings, and wrapper functions in the +# tool's own file. +# - `_source_cached_tool_init` is the preferred way to source expensive shell +# init output from CLIs such as `fzf`, `zoxide`, `fnm`, and `atuin`. + +_zsh_tools_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/zsh" +[[ -d "$_zsh_tools_cache_dir" ]] || mkdir -p "$_zsh_tools_cache_dir" + +_load_tool_config() { + local tool_name="$1" + local tool_file="$_zsh_tools_dir/${tool_name}.zsh" + + [[ -f "$tool_file" ]] && source "$tool_file" +} + +_source_cached_tool_init() { + local cache_name="$1" + local binary_name="$2" + local init_command="$3" + local binary_path + local cache_file="$_zsh_tools_cache_dir/${cache_name}.zsh" + local tmp_file="${cache_file}.tmp" + local version_file="$_zsh_tools_cache_dir/${cache_name}.version" + local current_version + + binary_path=$(command -v "$binary_name") || return 0 + current_version=$("$binary_path" --version 2>/dev/null | head -1) || current_version="unknown" + + if [[ -s "$cache_file" && -s "$version_file" ]]; then + local cached_version + cached_version=$(<"$version_file") + if [[ "$current_version" == "$cached_version" ]]; then + source "$cache_file" + return + fi + fi + + if eval "$init_command" >| "$tmp_file" 2>/dev/null; then + mv "$tmp_file" "$cache_file" + echo -n "$current_version" > "$version_file" + else + rm -f "$tmp_file" + eval "$init_command" + return + fi + + source "$cache_file" +} diff --git a/configs/shared/tools/atuin.zsh b/configs/shared/tools/atuin.zsh new file mode 100644 index 0000000..7d5da6a --- /dev/null +++ b/configs/shared/tools/atuin.zsh @@ -0,0 +1,14 @@ +# ============================================================================ +# atuin history integration +# ============================================================================ +# +# This module owns shell initialization for atuin. +# +# Maintenance rules: +# - Load atuin after fzf so atuin can override Ctrl-R with its history widget. +# - Do not place completion or Tab behavior here; keep atuin focused on history. +# - If atuin init ever conflicts with another history tool, document the final +# binding winner in `shared/tools.zsh`. + +# Atuin - load after fzf to override ctrl-r binding. +_source_cached_tool_init atuin-init atuin 'atuin init zsh' diff --git a/configs/shared/tools/bun.zsh b/configs/shared/tools/bun.zsh new file mode 100644 index 0000000..14a62ff --- /dev/null +++ b/configs/shared/tools/bun.zsh @@ -0,0 +1,16 @@ +# ============================================================================ +# bun completion +# ============================================================================ +# +# This module keeps bun completion isolated because it is optional and can be +# loaded slightly later without hurting the main interactive shell experience. +# +# Maintenance rules: +# - Keep this async unless there is a strong reason to make bun completion part +# of the synchronous startup path. +# - Only source the completion file when it exists on the current machine. + +{ + sleep 0.5 + [[ -s "$HOME/.bun/_bun" ]] && source "$HOME/.bun/_bun" +} &! diff --git a/configs/shared/tools/fzf.zsh b/configs/shared/tools/fzf.zsh new file mode 100644 index 0000000..5901351 --- /dev/null +++ b/configs/shared/tools/fzf.zsh @@ -0,0 +1,163 @@ +# ============================================================================ +# fzf configuration and Ctrl-T widget +# ============================================================================ +# +# This module owns all fzf-specific shell behavior in this config. +# +# Maintenance rules: +# - Keep `Tab` completion out of this file. `Tab` belongs to +# `shared/completion.zsh` and should remain native zsh completion. +# - `Ctrl-T` belongs here and should stay focused on interactive path picking. +# - Update preview behavior here if file/dir preview tools change. +# - If the partial-path completion logic changes, prefer small behavior changes +# and test `~/path`, `./path`, and relative nested paths before committing. + +# Note: home-directory branch returns nothing (no empty line) to avoid +# cluttering the picker when running fzf from $HOME. +export FZF_DEFAULT_COMMAND=' + if [[ "$PWD" != "$HOME" ]]; then + fd --type d --hidden --follow \ + --base-directory . \ + --exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \ + 2>/dev/null; + fd --type f --hidden --follow \ + --base-directory . \ + --exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \ + 2>/dev/null + fi +' + +export FZF_CTRL_T_COMMAND=$FZF_DEFAULT_COMMAND + +export FZF_CTRL_T_OPTS=" + --height 100% + --header '[C-/] toggle preview | [Alt-j/k] scroll preview' + --preview 'target={}; + if [[ \"$target\" != /* ]]; then + target=\"${FZF_CTRL_T_PREVIEW_ROOT:-$PWD}/$target\"; + fi; + if [ -f \"$target\" ]; then + bat --color=always --style=plain --line-range :300 \"$target\"; + elif [ -d \"$target\" ]; then + eza -L 2 -T --git-ignore \"$target\" 2>/dev/null | head -20; + fi' + --preview-window=right:50%:wrap + --bind 'ctrl-/:toggle-preview' + --bind 'alt-j:preview-down' + --bind 'alt-k:preview-up' + --bind 'ctrl-d:preview-page-down' + --bind 'ctrl-u:preview-page-up' +" + +_source_cached_tool_init fzf-init fzf 'fzf --zsh' + +_fzf_ctrl_t_command='if [[ -d "$FZF_CTRL_T_BASE" ]]; then + fd --type d --hidden --follow \ + --base-directory "$FZF_CTRL_T_BASE" \ + --exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \ + 2>/dev/null; + fd --type f --hidden --follow \ + --base-directory "$FZF_CTRL_T_BASE" \ + --exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \ + 2>/dev/null; +fi' + +_fzf_ctrl_t_path_context() { + local token="$1" + local expanded_token="$token" + local raw_dir expanded_dir + + if [[ "$token" != */* && "$token" != "~"* && "$token" != .* ]]; then + return 1 + fi + + case "$expanded_token" in + '~') + expanded_token="$HOME" + ;; + '~/'*) + expanded_token="$HOME/${expanded_token#~/}" + ;; + esac + + if [[ -d "$expanded_token" ]]; then + REPLY="$expanded_token" + REPLY2="${token%/}/" + REPLY3="" + return 0 + fi + + raw_dir="${token:h}" + expanded_dir="${expanded_token:h}" + if [[ "$expanded_dir" == "$expanded_token" || ! -d "$expanded_dir" ]]; then + return 1 + fi + + REPLY="$expanded_dir" + if [[ "$raw_dir" == '.' ]]; then + REPLY2="" + else + REPLY2="${raw_dir%/}/" + fi + REPLY3="${token:t}" +} + +_fzf_ctrl_t_select_from() { + setopt localoptions pipefail no_aliases 2>/dev/null + local base_dir="$1" + local insert_prefix="$2" + local query="$3" + local item + + FZF_CTRL_T_BASE="$base_dir" \ + FZF_CTRL_T_PREVIEW_ROOT="$base_dir" \ + FZF_DEFAULT_COMMAND="$_fzf_ctrl_t_command" \ + FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_CTRL_T_OPTS-} -m --query=${(qqq)query}") \ + FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) < /dev/tty | while read -r item; do + echo -n -E "${(q)insert_prefix$item} " + done + local ret=$? + echo + return $ret +} + +fzf-file-widget() { + setopt localoptions noshwordsplit noksh_arrays noposixbuiltins pipefail no_aliases 2>/dev/null + local -a tokens + local token base_dir insert_prefix query lbuf selected ret + + if [[ ${LBUFFER[-1]} == ' ' ]]; then + LBUFFER="${LBUFFER}$(__fzf_select)" + ret=$? + zle reset-prompt + return $ret + fi + + tokens=(${(z)LBUFFER}) + token="${tokens[-1]-}" + if [[ -z "$token" ]] || ! _fzf_ctrl_t_path_context "$token"; then + LBUFFER="${LBUFFER}$(__fzf_select)" + ret=$? + zle reset-prompt + return $ret + fi + + base_dir="$REPLY" + insert_prefix="$REPLY2" + query="$REPLY3" + lbuf="${LBUFFER:0:-${#token}}" + selected="$(_fzf_ctrl_t_select_from "$base_dir" "$insert_prefix" "$query")" + ret=$? + + if [[ -n "$selected" ]]; then + LBUFFER="$lbuf$selected" + fi + + zle reset-prompt + return $ret +} + +zle -N fzf-file-widget +bindkey -M emacs '^T' fzf-file-widget +bindkey -M vicmd '^T' fzf-file-widget +bindkey -M viins '^T' fzf-file-widget diff --git a/configs/shared/tools/runtime.zsh b/configs/shared/tools/runtime.zsh new file mode 100644 index 0000000..6beac1b --- /dev/null +++ b/configs/shared/tools/runtime.zsh @@ -0,0 +1,14 @@ +# ============================================================================ +# Runtime helper tools +# ============================================================================ +# +# This module groups lightweight shell integrations that do not own complex +# interactive behavior in this config. +# +# Maintenance rules: +# - Keep navigation/runtime init here when the tool only needs shell init. +# - If a tool starts to need custom widgets, preview logic, or wrappers, move it +# into its own file instead of growing this module. + +_source_cached_tool_init zoxide-init zoxide 'zoxide init zsh' +_source_cached_tool_init fnm-init fnm 'fnm env --use-on-cd --version-file-strategy=recursive --shell zsh' diff --git a/src/clean.js b/src/clean.js index d00f2b8..ceda8d3 100644 --- a/src/clean.js +++ b/src/clean.js @@ -15,7 +15,11 @@ const MANAGED_FILES = [ { path: ".config/zsh/core/paths.zsh", templates: [join(CONFIGS_DIR, "core", "paths.zsh")] }, { path: ".config/zsh/core/options.zsh", templates: [join(CONFIGS_DIR, "core", "options.zsh")] }, { path: ".config/zsh/shared/tools.zsh", templates: [join(CONFIGS_DIR, "shared", "tools.zsh")] }, - { path: ".config/zsh/shared/fzf.zsh", templates: [join(CONFIGS_DIR, "shared", "fzf.zsh")] }, + { path: ".config/zsh/shared/tools/_loader.zsh", templates: [join(CONFIGS_DIR, "shared", "tools", "_loader.zsh")] }, + { path: ".config/zsh/shared/tools/fzf.zsh", templates: [join(CONFIGS_DIR, "shared", "tools", "fzf.zsh")] }, + { path: ".config/zsh/shared/tools/runtime.zsh", templates: [join(CONFIGS_DIR, "shared", "tools", "runtime.zsh")] }, + { path: ".config/zsh/shared/tools/atuin.zsh", templates: [join(CONFIGS_DIR, "shared", "tools", "atuin.zsh")] }, + { path: ".config/zsh/shared/tools/bun.zsh", templates: [join(CONFIGS_DIR, "shared", "tools", "bun.zsh")] }, { path: ".config/zsh/shared/completion.zsh", templates: [join(CONFIGS_DIR, "shared", "completion.zsh")] }, { path: ".config/zsh/shared/highlighting.zsh", templates: [join(CONFIGS_DIR, "shared", "highlighting.zsh")] }, { path: ".config/zsh/shared/plugins.zsh", templates: [join(CONFIGS_DIR, "shared", "plugins.zsh")] }, @@ -28,9 +32,7 @@ const MANAGED_FILES = [ ], }, { path: ".config/zsh/local/machine.zsh", templates: [join(CONFIGS_DIR, "local", "machine.zsh")] }, - { path: ".config/suitup/aliases", templates: [join(CONFIGS_DIR, "aliases")] }, - { path: ".config/suitup/zinit-plugins", templates: [join(CONFIGS_DIR, "zinit-plugins")] }, - { path: ".config/suitup/config.vim", templates: [join(CONFIGS_DIR, "config.vim")] }, + { path: ".config/zsh/local/config.vim", templates: [join(CONFIGS_DIR, "config.vim")] }, ]; function displayPath(base, relativePath) { @@ -47,7 +49,7 @@ function matchesManagedTemplate(fullPath, templates) { } function listBackupDirs(base) { - const backupRoot = join(base, ".config", "suitup", "backups"); + const backupRoot = join(base, ".config", "zsh", "backups"); if (!existsSync(backupRoot)) { return []; } @@ -172,7 +174,7 @@ function cleanVimrc(base, summary) { return; } - const suitupVimConfig = join(base, ".config", "suitup", "config.vim"); + const suitupVimConfig = join(base, ".config", "zsh", "local", "config.vim"); const content = readFileSafe(vimrc); const cleaned = content .replace(new RegExp(`^source ${escapeRegExp(suitupVimConfig)}\\s*\\n?`, "m"), "") @@ -222,7 +224,7 @@ export function cleanSandbox(base) { cleanVimrc(base, summary); cleanManagedFiles(base, summary); - const backupsRoot = join(base, ".config", "suitup", "backups"); + const backupsRoot = join(base, ".config", "zsh", "backups"); if (existsSync(backupsRoot)) { rmSync(backupsRoot, { recursive: true, force: true }); } @@ -231,7 +233,7 @@ export function cleanSandbox(base) { new Set( MANAGED_FILES .map((managedFile) => dirname(managedFile.path)) - .concat([".config/zsh/local", ".config/zsh/shared", ".config/zsh/core", ".config/zsh", ".config/suitup"]) + .concat([".config/zsh/local", ".config/zsh/shared/tools", ".config/zsh/shared", ".config/zsh/core", ".config/zsh"]) ) ).sort((left, right) => right.length - left.length); diff --git a/src/setup.js b/src/setup.js index 4a2a9cd..662f1f9 100644 --- a/src/setup.js +++ b/src/setup.js @@ -64,7 +64,6 @@ export function detectCompletedSteps({ } = {}) { const completed = new Set(); const zshConfigDir = join(home, ".config", "zsh"); - const suitupDir = join(home, ".config", "suitup"); const xdgDataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share"); const zinitHome = join(xdgDataHome, "zinit", "zinit.git"); const zshrc = readFileSafe(join(home, ".zshrc")); @@ -83,7 +82,7 @@ export function detectCompletedSteps({ existsSync(join(zshConfigDir, "core", "paths.zsh")) && existsSync(join(zshConfigDir, "core", "options.zsh")) && existsSync(join(zshConfigDir, "shared", "tools.zsh")) && - existsSync(join(zshConfigDir, "shared", "fzf.zsh")) && + existsSync(join(zshConfigDir, "shared", "tools", "_loader.zsh")) && existsSync(join(zshConfigDir, "shared", "completion.zsh")) && existsSync(join(zshConfigDir, "shared", "highlighting.zsh")) && existsSync(join(zshConfigDir, "shared", "prompt.zsh")) && @@ -92,11 +91,11 @@ export function detectCompletedSteps({ completed.add("zsh-config"); } - if (existsSync(join(zshConfigDir, "shared", "plugins.zsh")) || existsSync(join(suitupDir, "zinit-plugins")) || existsSync(zinitHome)) { + if (existsSync(join(zshConfigDir, "shared", "plugins.zsh")) || existsSync(zinitHome)) { completed.add("plugins"); } - if (existsSync(join(zshConfigDir, "shared", "aliases.zsh")) || existsSync(join(suitupDir, "aliases"))) { + if (existsSync(join(zshConfigDir, "shared", "aliases.zsh"))) { completed.add("aliases"); } @@ -113,7 +112,7 @@ export function detectCompletedSteps({ completed.add("ssh"); } - if (existsSync(join(suitupDir, "config.vim")) && vimrc.includes("config.vim")) { + if (existsSync(join(zshConfigDir, "local", "config.vim")) && vimrc.includes("config.vim")) { completed.add("vim"); } diff --git a/src/steps/vim.js b/src/steps/vim.js index 5f36554..d294987 100644 --- a/src/steps/vim.js +++ b/src/steps/vim.js @@ -11,14 +11,14 @@ import { CONFIGS_DIR } from "../constants.js"; */ export async function setupVim({ home } = {}) { const base = home || homedir(); - const suitupDir = join(base, ".config", "suitup"); - const vimCfg = join(suitupDir, "config.vim"); + const zshLocal = join(base, ".config", "zsh", "local"); + const vimCfg = join(zshLocal, "config.vim"); const vimrc = join(base, ".vimrc"); // Copy vim config (skip if already exists) const copied = copyIfNotExists(join(CONFIGS_DIR, "config.vim"), vimCfg); if (copied) { - p.log.success("Vim config written to ~/.config/suitup/config.vim"); + p.log.success("Vim config written to ~/.config/zsh/local/config.vim"); } else { p.log.info("Vim config already exists, skipped"); } diff --git a/src/steps/zsh-config.js b/src/steps/zsh-config.js index a1b0296..b370091 100644 --- a/src/steps/zsh-config.js +++ b/src/steps/zsh-config.js @@ -24,7 +24,7 @@ export async function backupShellRcFiles({ home, reason = "setup" } = {}) { return null; } - const backupDir = join(base, ".config", "suitup", "backups", `${createBackupStamp()}-${reason}`); + const backupDir = join(base, ".config", "zsh", "backups", `${createBackupStamp()}-${reason}`); ensureDir(backupDir); for (const file of existingFiles) { @@ -50,13 +50,11 @@ export async function backupShellRcFiles({ home, reason = "setup" } = {}) { export async function setupZshConfig({ home, promptTheme = "p10k" } = {}) { const base = home || homedir(); const zshConfig = join(base, ".config", "zsh"); - const suitupConfig = join(base, ".config", "suitup"); // Ensure directories - for (const sub of ["core", "shared", "local"]) { + for (const sub of ["core", "shared", "shared/tools", "local"]) { ensureDir(join(zshConfig, sub)); } - ensureDir(suitupConfig); // Copy core configs (skip if already exist) const coreFiles = ["perf.zsh", "env.zsh", "paths.zsh", "options.zsh"]; @@ -68,7 +66,6 @@ export async function setupZshConfig({ home, promptTheme = "p10k" } = {}) { // Copy shared configs (skip if already exist) const sharedFiles = [ "tools.zsh", - "fzf.zsh", "completion.zsh", "highlighting.zsh", ]; @@ -77,6 +74,19 @@ export async function setupZshConfig({ home, promptTheme = "p10k" } = {}) { if (!copied) p.log.info(`Skipped shared/${file} (already exists)`); } + // Copy shared/tools/ module files (skip if already exist) + const toolFiles = [ + "_loader.zsh", + "fzf.zsh", + "runtime.zsh", + "atuin.zsh", + "bun.zsh", + ]; + for (const file of toolFiles) { + const copied = copyIfNotExists(join(CONFIGS_DIR, "shared", "tools", file), join(zshConfig, "shared", "tools", file)); + if (!copied) p.log.info(`Skipped shared/tools/${file} (already exists)`); + } + const promptSource = promptTheme === "basic" ? "prompt-basic.zsh" : "prompt.zsh"; const promptDest = join(zshConfig, "shared", "prompt.zsh"); const existingPrompt = readFileSafe(promptDest); @@ -132,7 +142,7 @@ export async function writeZshrc(pluginManager = "zinit", { home } = {}) { } writeFile(zshrc, template); - p.log.success(".zshrc written (backup saved under ~/.config/suitup/backups/)"); + p.log.success(".zshrc written (backup saved under ~/.config/zsh/backups/)"); } else { writeFile(zshrc, template); p.log.success(".zshrc created"); diff --git a/src/verify.js b/src/verify.js index 80dc820..01ee559 100644 --- a/src/verify.js +++ b/src/verify.js @@ -18,6 +18,11 @@ const CHECKS = { { path: ".config/zsh/core/paths.zsh", label: "~/.config/zsh/core/paths.zsh" }, { path: ".config/zsh/core/options.zsh", label: "~/.config/zsh/core/options.zsh" }, { path: ".config/zsh/shared/tools.zsh", label: "~/.config/zsh/shared/tools.zsh" }, + { path: ".config/zsh/shared/tools/_loader.zsh", label: "~/.config/zsh/shared/tools/_loader.zsh" }, + { path: ".config/zsh/shared/tools/fzf.zsh", label: "~/.config/zsh/shared/tools/fzf.zsh" }, + { path: ".config/zsh/shared/tools/runtime.zsh", label: "~/.config/zsh/shared/tools/runtime.zsh" }, + { path: ".config/zsh/shared/tools/atuin.zsh", label: "~/.config/zsh/shared/tools/atuin.zsh" }, + { path: ".config/zsh/shared/tools/bun.zsh", label: "~/.config/zsh/shared/tools/bun.zsh" }, { path: ".config/zsh/shared/completion.zsh", label: "~/.config/zsh/shared/completion.zsh" }, { path: ".config/zsh/shared/highlighting.zsh", label: "~/.config/zsh/shared/highlighting.zsh" }, { path: ".config/zsh/shared/plugins.zsh", label: "~/.config/zsh/shared/plugins.zsh" }, diff --git a/tests/append.test.js b/tests/append.test.js index 7f4f587..0137313 100644 --- a/tests/append.test.js +++ b/tests/append.test.js @@ -235,21 +235,20 @@ describe("fzf-config block", () => { rmSync(sandbox, { recursive: true, force: true }); }); - test("configs/shared/fzf.zsh exists as a standalone file", () => { - const fzfFile = join(CONFIGS_DIR, "shared", "fzf.zsh"); + test("configs/shared/tools/fzf.zsh exists as a tool module file", () => { + const fzfFile = join(CONFIGS_DIR, "shared", "tools", "fzf.zsh"); expect(existsSync(fzfFile)).toBe(true); }); test("fzf.zsh contains expected FZF environment variables", () => { - const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8"); + const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "tools", "fzf.zsh"), "utf-8"); expect(fzfContent).toContain("FZF_DEFAULT_COMMAND"); expect(fzfContent).toContain("FZF_CTRL_T_COMMAND"); expect(fzfContent).toContain("FZF_CTRL_T_OPTS"); }); - test("fzf.zsh does not contain tool-init cache helpers (those belong in tools.zsh)", () => { - const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8"); - expect(fzfContent).not.toContain("_source_cached_tool_init"); + test("fzf.zsh does not contain cache dir setup (that belongs in _loader.zsh)", () => { + const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "tools", "fzf.zsh"), "utf-8"); expect(fzfContent).not.toContain("_zsh_tools_cache_dir"); }); diff --git a/tests/clean.test.js b/tests/clean.test.js index 7ba0a8b..176b835 100644 --- a/tests/clean.test.js +++ b/tests/clean.test.js @@ -32,14 +32,18 @@ function writeManagedTree(base) { ["core/paths.zsh", ".config/zsh/core/paths.zsh"], ["core/options.zsh", ".config/zsh/core/options.zsh"], ["shared/tools.zsh", ".config/zsh/shared/tools.zsh"], - ["shared/fzf.zsh", ".config/zsh/shared/fzf.zsh"], + ["shared/tools/_loader.zsh", ".config/zsh/shared/tools/_loader.zsh"], + ["shared/tools/fzf.zsh", ".config/zsh/shared/tools/fzf.zsh"], + ["shared/tools/runtime.zsh", ".config/zsh/shared/tools/runtime.zsh"], + ["shared/tools/atuin.zsh", ".config/zsh/shared/tools/atuin.zsh"], + ["shared/tools/bun.zsh", ".config/zsh/shared/tools/bun.zsh"], ["shared/completion.zsh", ".config/zsh/shared/completion.zsh"], ["shared/highlighting.zsh", ".config/zsh/shared/highlighting.zsh"], ["shared/plugins.zsh", ".config/zsh/shared/plugins.zsh"], ["shared/aliases.zsh", ".config/zsh/shared/aliases.zsh"], ["shared/prompt.zsh", ".config/zsh/shared/prompt.zsh"], ["local/machine.zsh", ".config/zsh/local/machine.zsh"], - ["config.vim", ".config/suitup/config.vim"], + ["config.vim", ".config/zsh/local/config.vim"], ["zshrc.template", ".zshrc"], ["zshenv.template", ".zshenv"], ]; @@ -65,7 +69,7 @@ describe("clean command", () => { test("restores the latest non-suitup backups for managed zsh files", () => { writeManagedTree(sandbox.path); - const backupsRoot = join(sandbox.path, ".config", "suitup", "backups"); + const backupsRoot = join(sandbox.path, ".config", "zsh", "backups"); const olderBackup = join(backupsRoot, "2026-03-19T10-00-00-000Z-setup"); const newerBackup = join(backupsRoot, "2026-03-19T11-00-00-000Z-setup"); mkdirSync(olderBackup, { recursive: true }); @@ -89,7 +93,6 @@ describe("clean command", () => { expect(readFileSync(join(sandbox.path, ".zshrc"), "utf-8")).toContain("export FOO=bar"); expect(readFileSync(join(sandbox.path, ".zshenv"), "utf-8")).toContain("export BAR=baz"); expect(existsSync(join(sandbox.path, ".config", "zsh"))).toBe(false); - expect(existsSync(join(sandbox.path, ".config", "suitup"))).toBe(false); expect(summary.restored).toContain("~/.zshrc"); expect(summary.restored).toContain("~/.zshenv"); }); @@ -152,6 +155,6 @@ describe("clean command", () => { expect(result).toBeNull(); expect(existsSync(join(sandbox.path, ".config", "zsh"))).toBe(true); - expect(existsSync(join(sandbox.path, ".config", "suitup"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "local", "config.vim"))).toBe(true); }); }); diff --git a/tests/configs.test.js b/tests/configs.test.js index 414a043..291fb69 100644 --- a/tests/configs.test.js +++ b/tests/configs.test.js @@ -83,11 +83,17 @@ describe("Static config templates", () => { }); test("shared config files exist", () => { - for (const file of ["tools.zsh", "prompt.zsh", "prompt-basic.zsh", "fzf.zsh", "aliases.zsh", "plugins.zsh", "completion.zsh", "highlighting.zsh"]) { + for (const file of ["tools.zsh", "prompt.zsh", "prompt-basic.zsh", "aliases.zsh", "plugins.zsh", "completion.zsh", "highlighting.zsh"]) { expect(existsSync(join(CONFIGS_DIR, "shared", file))).toBe(true); } }); + test("shared/tools config files exist", () => { + for (const file of ["_loader.zsh", "fzf.zsh", "runtime.zsh", "atuin.zsh", "bun.zsh"]) { + expect(existsSync(join(CONFIGS_DIR, "shared", "tools", file))).toBe(true); + } + }); + test("zshrc templates exist", () => { expect(existsSync(join(CONFIGS_DIR, "zshrc.template"))).toBe(true); }); @@ -132,20 +138,22 @@ describe("Static config templates", () => { } }); - test("shared/fzf.zsh uses fd instead of rg for FZF_DEFAULT_COMMAND", () => { - const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8"); + test("shared/tools/fzf.zsh uses fd instead of rg for FZF_DEFAULT_COMMAND", () => { + const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "tools", "fzf.zsh"), "utf-8"); expect(fzfContent).toContain("fd --type"); expect(fzfContent).toContain("FZF_DEFAULT_COMMAND"); expect(fzfContent).toContain("FZF_CTRL_T_COMMAND"); expect(fzfContent).toContain("FZF_CTRL_T_OPTS"); }); - test("shared/tools.zsh contains tool-init helpers and sources fzf.zsh", () => { + test("shared/tools.zsh is a thin orchestrator that loads tool configs", () => { const content = readFileSync(join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8"); - expect(content).toContain("fnm"); + expect(content).toContain("_load_tool_config"); + expect(content).toContain("fzf"); + expect(content).toContain("runtime"); expect(content).toContain("atuin"); - expect(content).toContain("zoxide"); - expect(content).toContain("fzf.zsh"); + expect(content).toContain("bun"); + expect(content).not.toContain("_source_cached_tool_init"); }); test("shared/completion.zsh configures cached compinit", () => { @@ -172,8 +180,8 @@ describe("Static config templates", () => { test("shared/tools.zsh loads fzf before atuin so atuin keeps the Ctrl-R binding", () => { const content = readFileSync(join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8"); - const fzfIdx = content.indexOf("_source_cached_tool_init fzf-init fzf 'fzf --zsh'"); - const atuinIdx = content.indexOf("_source_cached_tool_init atuin-init atuin 'atuin init zsh'"); + const fzfIdx = content.indexOf("_load_tool_config fzf"); + const atuinIdx = content.indexOf("_load_tool_config atuin"); expect(fzfIdx).toBeGreaterThan(-1); expect(atuinIdx).toBeGreaterThan(-1); @@ -187,7 +195,11 @@ describe("Static config templates", () => { "core/paths.zsh", "core/options.zsh", "shared/tools.zsh", - "shared/fzf.zsh", + "shared/tools/_loader.zsh", + "shared/tools/fzf.zsh", + "shared/tools/runtime.zsh", + "shared/tools/atuin.zsh", + "shared/tools/bun.zsh", "shared/completion.zsh", "shared/highlighting.zsh", "shared/plugins.zsh", @@ -224,7 +236,11 @@ describe("Static config templates", () => { "core/paths.zsh", "core/options.zsh", "shared/tools.zsh", - "shared/fzf.zsh", + "shared/tools/_loader.zsh", + "shared/tools/fzf.zsh", + "shared/tools/runtime.zsh", + "shared/tools/atuin.zsh", + "shared/tools/bun.zsh", "shared/completion.zsh", "shared/highlighting.zsh", "shared/plugins.zsh", @@ -292,7 +308,8 @@ describe("perf.zsh EPOCHREALTIME guard", () => { }); test("normal path _zsh_report outputs a timing table", () => { - expect(perf).toContain("┌──────────────────────────┐"); + expect(perf).toContain("┌────────────────────────────┐"); expect(perf).toContain("total"); + expect(perf).toContain("completion cache"); }); }); diff --git a/tests/setup.test.js b/tests/setup.test.js index 4efa34e..9583d4c 100644 --- a/tests/setup.test.js +++ b/tests/setup.test.js @@ -39,6 +39,7 @@ describe("Setup simulation in sandbox", () => { ".config/zsh/core", ".config/zsh/shared", ".config/zsh/local", + ".config/zsh/shared/tools", ]; for (const dir of dirs) { mkdirSync(join(sandbox, dir), { recursive: true }); @@ -53,13 +54,21 @@ describe("Setup simulation in sandbox", () => { } // Copy shared configs - for (const file of ["tools.zsh", "fzf.zsh", "prompt.zsh", "completion.zsh", "highlighting.zsh", "aliases.zsh", "plugins.zsh"]) { + for (const file of ["tools.zsh", "prompt.zsh", "completion.zsh", "highlighting.zsh", "aliases.zsh", "plugins.zsh"]) { copyFileSync( join(CONFIGS_DIR, "shared", file), join(sandbox, ".config/zsh/shared", file) ); } + // Copy shared/tools configs + for (const file of ["_loader.zsh", "fzf.zsh", "runtime.zsh", "atuin.zsh", "bun.zsh"]) { + copyFileSync( + join(CONFIGS_DIR, "shared", "tools", file), + join(sandbox, ".config/zsh/shared/tools", file) + ); + } + copyFileSync( join(CONFIGS_DIR, "local", "machine.zsh"), join(sandbox, ".config/zsh/local", "machine.zsh") @@ -79,7 +88,11 @@ describe("Setup simulation in sandbox", () => { ".config/zsh/core/paths.zsh", ".config/zsh/core/options.zsh", ".config/zsh/shared/tools.zsh", - ".config/zsh/shared/fzf.zsh", + ".config/zsh/shared/tools/_loader.zsh", + ".config/zsh/shared/tools/fzf.zsh", + ".config/zsh/shared/tools/runtime.zsh", + ".config/zsh/shared/tools/atuin.zsh", + ".config/zsh/shared/tools/bun.zsh", ".config/zsh/shared/completion.zsh", ".config/zsh/shared/highlighting.zsh", ".config/zsh/shared/aliases.zsh", @@ -154,6 +167,7 @@ describe("Setup simulation in sandbox", () => { mkdirSync(join(sandbox, ".config", "zsh", "core"), { recursive: true }); mkdirSync(join(sandbox, ".config", "zsh", "shared"), { recursive: true }); mkdirSync(join(sandbox, ".config", "zsh", "local"), { recursive: true }); + mkdirSync(join(sandbox, ".config", "zsh", "shared", "tools"), { recursive: true }); mkdirSync(join(sandbox, ".local", "share", "zinit", "zinit.git"), { recursive: true }); writeFileSync(join(sandbox, ".zshrc"), "# Generated by suitup\n", "utf-8"); @@ -163,7 +177,7 @@ describe("Setup simulation in sandbox", () => { writeFileSync(join(sandbox, ".config", "zsh", "core", "paths.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "core", "options.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "tools.zsh"), "", "utf-8"); - writeFileSync(join(sandbox, ".config", "zsh", "shared", "fzf.zsh"), "", "utf-8"); + writeFileSync(join(sandbox, ".config", "zsh", "shared", "tools", "_loader.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "completion.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "highlighting.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "plugins.zsh"), "", "utf-8"); @@ -194,6 +208,7 @@ describe("Setup simulation in sandbox", () => { mkdirSync(join(completedSandbox.path, ".config", "zsh", "core"), { recursive: true }); mkdirSync(join(completedSandbox.path, ".config", "zsh", "shared"), { recursive: true }); mkdirSync(join(completedSandbox.path, ".config", "zsh", "local"), { recursive: true }); + mkdirSync(join(completedSandbox.path, ".config", "zsh", "shared", "tools"), { recursive: true }); writeFileSync(join(completedSandbox.path, ".zshrc"), "# Generated by suitup\n", "utf-8"); writeFileSync(join(completedSandbox.path, ".zshenv"), "# Generated by suitup\n", "utf-8"); @@ -202,7 +217,7 @@ describe("Setup simulation in sandbox", () => { writeFileSync(join(completedSandbox.path, ".config", "zsh", "core", "paths.zsh"), "", "utf-8"); writeFileSync(join(completedSandbox.path, ".config", "zsh", "core", "options.zsh"), "", "utf-8"); writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "tools.zsh"), "", "utf-8"); - writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "fzf.zsh"), "", "utf-8"); + writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "tools", "_loader.zsh"), "", "utf-8"); writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "completion.zsh"), "", "utf-8"); writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "highlighting.zsh"), "", "utf-8"); writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "prompt.zsh"), "", "utf-8"); @@ -230,6 +245,7 @@ describe("Setup simulation in sandbox", () => { mkdirSync(join(sandbox, ".config", "zsh", "core"), { recursive: true }); mkdirSync(join(sandbox, ".config", "zsh", "shared"), { recursive: true }); mkdirSync(join(sandbox, ".config", "zsh", "local"), { recursive: true }); + mkdirSync(join(sandbox, ".config", "zsh", "shared", "tools"), { recursive: true }); writeFileSync(join(sandbox, ".zshrc"), "# user managed\n", "utf-8"); writeFileSync(join(sandbox, ".zshenv"), "# user managed\n", "utf-8"); @@ -238,7 +254,7 @@ describe("Setup simulation in sandbox", () => { writeFileSync(join(sandbox, ".config", "zsh", "core", "paths.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "core", "options.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "tools.zsh"), "", "utf-8"); - writeFileSync(join(sandbox, ".config", "zsh", "shared", "fzf.zsh"), "", "utf-8"); + writeFileSync(join(sandbox, ".config", "zsh", "shared", "tools", "_loader.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "completion.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "highlighting.zsh"), "", "utf-8"); writeFileSync(join(sandbox, ".config", "zsh", "shared", "prompt.zsh"), "", "utf-8"); @@ -263,23 +279,36 @@ describe("Setup simulation in sandbox", () => { expect(content).not.toMatch(/\/Users\/\w+/); }); - test("tools.zsh uses fnm instead of volta", () => { + test("tools.zsh orchestrator loads runtime.zsh which uses fnm", () => { const content = readFileSync( join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8" ); - expect(content).toContain("fnm"); + expect(content).toContain("_load_tool_config"); + expect(content).toContain("runtime"); expect(content).not.toContain("volta"); + + const runtime = readFileSync( + join(CONFIGS_DIR, "shared", "tools", "runtime.zsh"), + "utf-8" + ); + expect(runtime).toContain("fnm"); }); - test("tools.zsh uses zoxide instead of autojump", () => { + test("tools.zsh orchestrator loads runtime.zsh which uses zoxide", () => { const content = readFileSync( join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8" ); - expect(content).toContain("zoxide"); + expect(content).toContain("_load_tool_config"); expect(content).not.toContain("autojump"); + + const runtime = readFileSync( + join(CONFIGS_DIR, "shared", "tools", "runtime.zsh"), + "utf-8" + ); + expect(runtime).toContain("zoxide"); }); }); diff --git a/tests/verify.test.js b/tests/verify.test.js index 1e5dd77..7103fcc 100644 --- a/tests/verify.test.js +++ b/tests/verify.test.js @@ -36,6 +36,7 @@ describe("Verify in sandbox", () => { ".config/zsh/core", ".config/zsh/shared", ".config/zsh/local", + ".config/zsh/shared/tools", ]; for (const dir of dirs) { mkdirSync(join(sandbox, dir), { recursive: true }); @@ -48,6 +49,11 @@ describe("Verify in sandbox", () => { ["core/paths.zsh", ".config/zsh/core/paths.zsh"], ["core/options.zsh", ".config/zsh/core/options.zsh"], ["shared/tools.zsh", ".config/zsh/shared/tools.zsh"], + ["shared/tools/_loader.zsh", ".config/zsh/shared/tools/_loader.zsh"], + ["shared/tools/fzf.zsh", ".config/zsh/shared/tools/fzf.zsh"], + ["shared/tools/runtime.zsh", ".config/zsh/shared/tools/runtime.zsh"], + ["shared/tools/atuin.zsh", ".config/zsh/shared/tools/atuin.zsh"], + ["shared/tools/bun.zsh", ".config/zsh/shared/tools/bun.zsh"], ["shared/completion.zsh", ".config/zsh/shared/completion.zsh"], ["shared/highlighting.zsh", ".config/zsh/shared/highlighting.zsh"], ["shared/plugins.zsh", ".config/zsh/shared/plugins.zsh"], diff --git a/tests/zsh-config-steps.test.js b/tests/zsh-config-steps.test.js index 45fc572..8111292 100644 --- a/tests/zsh-config-steps.test.js +++ b/tests/zsh-config-steps.test.js @@ -39,7 +39,11 @@ describe("zsh-config step", () => { expect(existsSync(join(sandbox.path, ".config", "zsh", "core", "paths.zsh"))).toBe(true); expect(existsSync(join(sandbox.path, ".config", "zsh", "core", "options.zsh"))).toBe(true); expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools.zsh"))).toBe(true); - expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "fzf.zsh"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "_loader.zsh"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "fzf.zsh"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "runtime.zsh"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "atuin.zsh"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "bun.zsh"))).toBe(true); expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "completion.zsh"))).toBe(true); expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "highlighting.zsh"))).toBe(true); expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "prompt.zsh"))).toBe(true); @@ -51,16 +55,18 @@ describe("zsh-config step", () => { const perf = readFileSync(join(sandbox.path, ".config", "zsh", "core", "perf.zsh"), "utf-8"); const tools = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "tools.zsh"), "utf-8"); - const fzf = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "fzf.zsh"), "utf-8"); + const loader = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "_loader.zsh"), "utf-8"); + const fzfTool = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "tools", "fzf.zsh"), "utf-8"); const completion = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "completion.zsh"), "utf-8"); const highlighting = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "highlighting.zsh"), "utf-8"); expect(perf).toContain("EPOCHREALTIME"); expect(perf).toContain("_record_stage_duration"); - expect(tools).toContain("_source_cached_tool_init"); - expect(tools).toContain("$_zsh_tools_cache_dir"); - expect(fzf).toContain("FZF_DEFAULT_COMMAND"); - expect(fzf).toContain("FZF_CTRL_T_OPTS"); + expect(tools).toContain("_load_tool_config"); + expect(tools).toContain("_zsh_tools_dir"); + expect(loader).toContain("_source_cached_tool_init"); + expect(fzfTool).toContain("FZF_DEFAULT_COMMAND"); + expect(fzfTool).toContain("fzf-file-widget"); expect(completion).toContain("compinit"); expect(highlighting).toContain("ZSH_HIGHLIGHT_STYLES"); }); @@ -129,7 +135,7 @@ describe("zsh-config step", () => { const content = readFileSync(join(sandbox.path, ".zshrc"), "utf-8"); expect(content).toContain("ZSH_CONFIG"); - const backupRoot = join(sandbox.path, ".config", "suitup", "backups"); + const backupRoot = join(sandbox.path, ".config", "zsh", "backups"); const backupDirs = readdirSync(backupRoot); expect(backupDirs.length).toBe(1); @@ -238,7 +244,7 @@ describe("vim step", () => { test("writes vim config and .vimrc in empty sandbox", async () => { await setupVim({ home: sandbox.path }); - expect(existsSync(join(sandbox.path, ".config", "suitup", "config.vim"))).toBe(true); + expect(existsSync(join(sandbox.path, ".config", "zsh", "local", "config.vim"))).toBe(true); expect(existsSync(join(sandbox.path, ".vimrc"))).toBe(true); const vimrc = readFileSync(join(sandbox.path, ".vimrc"), "utf-8"); @@ -247,12 +253,12 @@ describe("vim step", () => { }); test("skips vim config when already exists", async () => { - mkdirSync(join(sandbox.path, ".config", "suitup"), { recursive: true }); - writeFileSync(join(sandbox.path, ".config", "suitup", "config.vim"), "\" my vim config", "utf-8"); + mkdirSync(join(sandbox.path, ".config", "zsh", "local"), { recursive: true }); + writeFileSync(join(sandbox.path, ".config", "zsh", "local", "config.vim"), "\" my vim config", "utf-8"); await setupVim({ home: sandbox.path }); - const content = readFileSync(join(sandbox.path, ".config", "suitup", "config.vim"), "utf-8"); + const content = readFileSync(join(sandbox.path, ".config", "zsh", "local", "config.vim"), "utf-8"); expect(content).toBe("\" my vim config"); });