diff --git a/src/append.js b/src/append.js index c367478..2394f83 100644 --- a/src/append.js +++ b/src/append.js @@ -3,7 +3,7 @@ import pc from "picocolors"; import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; -import { appendIfMissing, ensureDir, readFileSafe, copyFile, writeFile } from "./utils/fs.js"; +import { appendIfMissing, ensureDir, readFileSafe, copyFile, copyIfNotExists, writeFile } from "./utils/fs.js"; import { CONFIGS_DIR } from "./constants.js"; import { backupShellRcFiles } from "./steps/zsh-config.js"; import { installZinit } from "./steps/plugin-manager.js"; @@ -154,13 +154,14 @@ const BLOCKS = [ }, async apply() { const installed = await ensureToolsInitDependencies(); + const zshSharedDir = join(homedir(), ".config", "zsh", "shared"); + ensureDir(zshSharedDir); + copyIfNotExists(join(CONFIGS_DIR, "shared", "tools.zsh"), join(zshSharedDir, "tools.zsh")); + copyIfNotExists(join(CONFIGS_DIR, "shared", "fzf.zsh"), join(zshSharedDir, "fzf.zsh")); const block = [ "", "# >>> suitup/tools-init >>>", - 'command -v atuin &>/dev/null && eval "$(atuin init zsh)"', - 'command -v fzf &>/dev/null && eval "$(fzf --zsh)"', - 'command -v zoxide &>/dev/null && eval "$(zoxide init zsh)"', - 'command -v fnm &>/dev/null && eval "$(fnm env --use-on-cd --version-file-strategy=recursive --shell zsh)"', + 'source_if_exists "$HOME/.config/zsh/shared/tools.zsh"', '[[ -s "$HOME/.bun/_bun" ]] && source "$HOME/.bun/_bun"', "# <<< suitup/tools-init <<<", "", @@ -176,8 +177,10 @@ const BLOCKS = [ group: "Shell Enhancements", marker: "suitup/zsh-options", apply() { - const content = readFileSync(join(CONFIGS_DIR, "core", "options.zsh"), "utf-8"); - const block = `\n# >>> suitup/zsh-options >>>\n${content}\n# <<< suitup/zsh-options <<<\n`; + const dest = join(homedir(), ".config", "zsh", "core", "options.zsh"); + ensureDir(join(homedir(), ".config", "zsh", "core")); + copyIfNotExists(join(CONFIGS_DIR, "core", "options.zsh"), dest); + const block = '\n# >>> suitup/zsh-options >>>\nsource_if_exists "$HOME/.config/zsh/core/options.zsh"\n# <<< suitup/zsh-options <<<\n'; return appendIfMissing(ZSHRC, block, "suitup/zsh-options"); }, }, @@ -188,8 +191,10 @@ const BLOCKS = [ group: "Shell Enhancements", marker: "suitup/env", apply() { - const content = readFileSync(join(CONFIGS_DIR, "core", "env.zsh"), "utf-8"); - const block = `\n# >>> suitup/env >>>\n${content}\n# <<< suitup/env <<<\n`; + const dest = join(homedir(), ".config", "zsh", "core", "env.zsh"); + ensureDir(join(homedir(), ".config", "zsh", "core")); + copyIfNotExists(join(CONFIGS_DIR, "core", "env.zsh"), dest); + const block = '\n# >>> suitup/env >>>\nsource_if_exists "$HOME/.config/zsh/core/env.zsh"\n# <<< suitup/env <<<\n'; return appendIfMissing(ZSHRC, block, "suitup/env"); }, }, @@ -200,8 +205,10 @@ const BLOCKS = [ group: "Advanced", marker: "suitup/perf", apply() { - const content = readFileSync(join(CONFIGS_DIR, "core", "perf.zsh"), "utf-8"); - const block = `\n# >>> suitup/perf >>>\n${content}\n# <<< suitup/perf <<<\n`; + const dest = join(homedir(), ".config", "zsh", "core", "perf.zsh"); + ensureDir(join(homedir(), ".config", "zsh", "core")); + copyIfNotExists(join(CONFIGS_DIR, "core", "perf.zsh"), dest); + const block = '\n# >>> suitup/perf >>>\nsource_if_exists "$HOME/.config/zsh/core/perf.zsh"\n# <<< suitup/perf <<<\n'; return appendIfMissing(ZSHRC, block, "suitup/perf"); }, }, @@ -212,8 +219,10 @@ const BLOCKS = [ group: "Advanced", marker: "suitup/fzf-config", apply() { - const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8"); - const block = `\n# >>> suitup/fzf-config >>>\n${fzfContent.trim()}\n# <<< suitup/fzf-config <<<\n`; + const dest = join(homedir(), ".config", "zsh", "shared", "fzf.zsh"); + ensureDir(join(homedir(), ".config", "zsh", "shared")); + copyIfNotExists(join(CONFIGS_DIR, "shared", "fzf.zsh"), dest); + const block = '\n# >>> suitup/fzf-config >>>\nsource_if_exists "$HOME/.config/zsh/shared/fzf.zsh"\n# <<< suitup/fzf-config <<<\n'; return appendIfMissing(ZSHRC, block, "suitup/fzf-config"); }, }, diff --git a/tests/append.test.js b/tests/append.test.js index 52c5079..4d72362 100644 --- a/tests/append.test.js +++ b/tests/append.test.js @@ -166,10 +166,8 @@ describe("Append mode utilities", () => { test("tools-init repair is needed when config exists but fnm is missing", () => { const existing = [ "# >>> suitup/tools-init >>>", - 'command -v atuin &>/dev/null && eval "$(atuin init zsh)"', - 'command -v fzf &>/dev/null && eval "$(fzf --zsh)"', - 'command -v zoxide &>/dev/null && eval "$(zoxide init zsh)"', - 'command -v fnm &>/dev/null && eval "$(fnm env --use-on-cd --version-file-strategy=recursive --shell zsh)"', + 'source_if_exists "$HOME/.config/zsh/shared/tools.zsh"', + '[[ -s "$HOME/.bun/_bun" ]] && source "$HOME/.bun/_bun"', "# <<< suitup/tools-init <<<", "", ].join("\n"); @@ -199,6 +197,28 @@ describe("Append mode utilities", () => { expect(installCliTools).not.toHaveBeenCalled(); expect(installFrontendTools).not.toHaveBeenCalled(); }); + test("tools-init block sources tools.zsh instead of inline eval commands", () => { + writeFileSync(zshrcPath, '# existing config\nsource_if_exists() { [[ -f "$1" ]] && source "$1"; }\n', "utf-8"); + + const block = [ + "", + "# >>> suitup/tools-init >>>", + 'source_if_exists "$HOME/.config/zsh/shared/tools.zsh"', + '[[ -s "$HOME/.bun/_bun" ]] && source "$HOME/.bun/_bun"', + "# <<< suitup/tools-init <<<", + "", + ].join("\n"); + + const result = appendIfMissing(zshrcPath, block, "suitup/tools-init"); + + expect(result).toBe(true); + const content = readFileSync(zshrcPath, "utf-8"); + // Should source the managed tools.zsh file + expect(content).toContain('source_if_exists "$HOME/.config/zsh/shared/tools.zsh"'); + // Should NOT contain inline eval commands + expect(content).not.toContain('eval "$(atuin init zsh)"'); + expect(content).not.toContain('eval "$(fzf --zsh)"'); + }); }); describe("fzf-config block", () => { @@ -239,27 +259,26 @@ describe("fzf-config block", () => { expect(toolsContent).not.toContain("FZF_CTRL_T_OPTS="); }); - test("fzf-config block appends fzf.zsh content directly (no brittle string splitting)", () => { - writeFileSync(zshrcPath, "# base\n", "utf-8"); + test("fzf-config block sources fzf.zsh instead of inlining content", () => { + writeFileSync(zshrcPath, '# base\nsource_if_exists() { [[ -f "$1" ]] && source "$1"; }\n', "utf-8"); - const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8").trim(); - const block = `\n# >>> suitup/fzf-config >>>\n${fzfContent}\n# <<< suitup/fzf-config <<<\n`; + const block = '\n# >>> suitup/fzf-config >>>\nsource_if_exists "$HOME/.config/zsh/shared/fzf.zsh"\n# <<< suitup/fzf-config <<<\n'; const result = appendIfMissing(zshrcPath, block, "suitup/fzf-config"); expect(result).toBe(true); const written = readFileSync(zshrcPath, "utf-8"); - expect(written).toContain("FZF_DEFAULT_COMMAND"); - expect(written).toContain("FZF_CTRL_T_OPTS"); + expect(written).toContain('source_if_exists "$HOME/.config/zsh/shared/fzf.zsh"'); expect(written).toContain("# >>> suitup/fzf-config >>>"); expect(written).toContain("# <<< suitup/fzf-config <<<"); + // Should NOT inline FZF env vars directly + expect(written).not.toContain("FZF_DEFAULT_COMMAND"); }); test("fzf-config block is idempotent — double append does not duplicate", () => { writeFileSync(zshrcPath, "# base\n", "utf-8"); - const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8").trim(); - const block = `\n# >>> suitup/fzf-config >>>\n${fzfContent}\n# <<< suitup/fzf-config <<<\n`; + const block = '\n# >>> suitup/fzf-config >>>\nsource_if_exists "$HOME/.config/zsh/shared/fzf.zsh"\n# <<< suitup/fzf-config <<<\n'; appendIfMissing(zshrcPath, block, "suitup/fzf-config"); appendIfMissing(zshrcPath, block, "suitup/fzf-config"); @@ -270,3 +289,59 @@ describe("fzf-config block", () => { expect(matches.length).toBe(2); }); }); + +describe("append blocks source managed files instead of inlining", () => { + let sandbox; + let zshrcPath; + + beforeEach(() => { + vi.clearAllMocks(); + sandbox = mkdtempSync(join(tmpdir(), "suitup-test-")); + zshrcPath = join(sandbox, ".zshrc"); + }); + + afterEach(() => { + rmSync(sandbox, { recursive: true, force: true }); + }); + + test("zsh-options block sources core/options.zsh instead of inlining", () => { + writeFileSync(zshrcPath, "# base\n", "utf-8"); + + const block = '\n# >>> suitup/zsh-options >>>\nsource_if_exists "$HOME/.config/zsh/core/options.zsh"\n# <<< suitup/zsh-options <<<\n'; + const result = appendIfMissing(zshrcPath, block, "suitup/zsh-options"); + + expect(result).toBe(true); + const content = readFileSync(zshrcPath, "utf-8"); + expect(content).toContain('source_if_exists "$HOME/.config/zsh/core/options.zsh"'); + // Should NOT inline the options content + expect(content).not.toContain("setopt AUTO_CD"); + expect(content).not.toContain("HISTSIZE"); + }); + + test("env-vars block sources core/env.zsh instead of inlining", () => { + writeFileSync(zshrcPath, "# base\n", "utf-8"); + + const block = '\n# >>> suitup/env >>>\nsource_if_exists "$HOME/.config/zsh/core/env.zsh"\n# <<< suitup/env <<<\n'; + const result = appendIfMissing(zshrcPath, block, "suitup/env"); + + expect(result).toBe(true); + const content = readFileSync(zshrcPath, "utf-8"); + expect(content).toContain('source_if_exists "$HOME/.config/zsh/core/env.zsh"'); + // Should NOT inline the env content + expect(content).not.toContain("BAT_THEME"); + }); + + test("perf block sources core/perf.zsh instead of inlining", () => { + writeFileSync(zshrcPath, "# base\n", "utf-8"); + + const block = '\n# >>> suitup/perf >>>\nsource_if_exists "$HOME/.config/zsh/core/perf.zsh"\n# <<< suitup/perf <<<\n'; + const result = appendIfMissing(zshrcPath, block, "suitup/perf"); + + expect(result).toBe(true); + const content = readFileSync(zshrcPath, "utf-8"); + expect(content).toContain('source_if_exists "$HOME/.config/zsh/core/perf.zsh"'); + // Should NOT inline the perf content + expect(content).not.toContain("EPOCHREALTIME"); + expect(content).not.toContain("_zsh_report"); + }); +});