Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions src/append.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 <<<",
"",
Expand All @@ -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");
},
},
Expand All @@ -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");
},
},
Expand All @@ -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");
},
},
Expand All @@ -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");
},
},
Expand Down
99 changes: 87 additions & 12 deletions tests/append.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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");
Expand All @@ -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");
});
});
Loading