From a8b2412ac736e68236beb14dd4b2b70eecbdf24a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 16:21:49 +0000 Subject: [PATCH 1/3] Initial plan From b6312e20522015451e70537227e68de90e48e366 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 16:25:18 +0000 Subject: [PATCH 2/3] fix: tools-init append reuses managed tools.zsh instead of inline evals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #25 — the append mode now sources ~/.config/zsh/shared/tools.zsh (which has the correct fzf-before-atuin order and caching) instead of generating inline eval commands with the wrong order that broke atuin's Ctrl-R binding. Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> Agent-Logs-Url: https://github.com/ChangeHow/suitup/sessions/c4e1ed01-6e6f-4627-8e2a-6713a92a1d55 --- src/append.js | 11 ++++++----- tests/append.test.js | 28 ++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/append.js b/src/append.js index c367478..eed11b3 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 <<<", "", diff --git a/tests/append.test.js b/tests/append.test.js index 52c5079..7d64e3f 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", () => { From 36e119a073ce61fca6e9598404cbf24874e71120 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 16:35:03 +0000 Subject: [PATCH 3/3] fix: all append blocks copy+source managed config files instead of inlining zsh-options, env-vars, perf, and fzf-config blocks now copy config files to ~/.config/zsh/ and source them, consistent with tools-init. Prevents duplication if user later runs setup. Co-authored-by: ChangeHow <23733347+ChangeHow@users.noreply.github.com> Agent-Logs-Url: https://github.com/ChangeHow/suitup/sessions/a9f7a54f-f6ca-4022-89ec-c7daa9cd773b --- src/append.js | 24 ++++++++++----- tests/append.test.js | 71 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/src/append.js b/src/append.js index eed11b3..2394f83 100644 --- a/src/append.js +++ b/src/append.js @@ -177,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"); }, }, @@ -189,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"); }, }, @@ -201,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"); }, }, @@ -213,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 7d64e3f..4d72362 100644 --- a/tests/append.test.js +++ b/tests/append.test.js @@ -259,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"); @@ -290,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"); + }); +});