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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Interactive step-by-step setup with selectable steps:
4. **Prompt Preset** — Powerlevel10k (recommended) or a basic zsh prompt
5. **CLI Tools** — bat, eza, fzf, fd, zoxide, atuin, ripgrep...
6. **GUI Apps** — optional Ghostty, Raycast, VS Code, fonts...
7. **Frontend Tools** — choose fnm, Node.js via fnm, pnpm, git-cz
7. **Frontend Tools** — choose fnm, Node.js via fnm, pnpm, Bun, git-cz
8. **Shell Aliases** — git, eza, fzf shortcuts
9. **SSH Key** — generate GitHub SSH key
10. **Vim Config** — basic vim setup
Expand All @@ -141,6 +141,7 @@ Bootstrap details:
- Suitup now also writes a minimal `~/.zshenv` so non-interactive shells can still load shared env vars and PATH setup
- When fnm installs Node.js, suitup keeps both the `fnm` binary and the installed default Node version on PATH so `fnm`, `node`, `npm`, and globally installed CLIs resolve correctly in both interactive and non-interactive shells
- Frontend tool installs use a user-local npm prefix under `~/.local/` so `pnpm` and `git-cz` do not require sudo or writes to system npm directories
- Suitup installs Bun with its official installer and exposes `~/.bun/bin` through the managed PATH configuration

### Append

Expand Down
3 changes: 2 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ node src/cli.js
4. **Prompt Preset** — Powerlevel10k(推荐)或基础 zsh prompt
5. **CLI Tools** — bat、eza、fzf、fd、zoxide、atuin、ripgrep 等
6. **GUI Apps** — 可选的 Ghostty、Raycast、VS Code、字体等
7. **Frontend Tools** — 可选 fnm、通过 fnm 安装的 Node.js、pnpm、git-cz
7. **Frontend Tools** — 可选 fnm、通过 fnm 安装的 Node.js、pnpm、Bun、git-cz
8. **Shell Aliases** — git、eza、fzf 等快捷命令
9. **SSH Key** — 生成 GitHub SSH 密钥
10. **Vim Config** — 基础 Vim 配置
Expand All @@ -141,6 +141,7 @@ Bootstrap 细节:
- suitup 也会生成一个精简的 `~/.zshenv`,保证非交互式 shell 也能加载共享环境变量和 PATH
- 当 fnm 安装 Node.js 后,suitup 会把 `fnm` 自身和该默认 Node 版本一起放进 PATH,确保交互式/非交互式 shell 下的 `fnm`、`node`、`npm` 和全局 CLI 都能正确解析
- Frontend 工具安装会使用 `~/.local/` 下的用户级 npm prefix,这样安装 `pnpm` 和 `git-cz` 不需要 sudo,也不会写入系统级 npm 目录
- suitup 会通过 Bun 官方安装器完成安装,并由托管的 PATH 配置加载 `~/.bun/bin`

### Append(追加)

Expand Down
8 changes: 8 additions & 0 deletions configs/core/paths.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ if [[ -d "$_suitup_fnm_default_bin" && ":${PATH}:" != *":${_suitup_fnm_default_b
fi
unset _suitup_fnm_dir _suitup_fnm_default_bin

# Bun's official installer uses ~/.bun by default. Keep its runtime available
# to interactive and non-interactive shells without relying on installer-added
# startup snippets.
export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"
if [[ -d "$BUN_INSTALL/bin" && ":${PATH}:" != *":${BUN_INSTALL}/bin:"* ]]; then
export PATH="${BUN_INSTALL}/bin:${PATH}"
fi

# Keep this file for user PATH overrides if needed.
12 changes: 6 additions & 6 deletions configs/shared/tools/fzf.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ 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\";
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;
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'
Expand Down
2 changes: 1 addition & 1 deletion src/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export async function runSetup({ defaults = false } = {}) {
{ value: "plugins", label: "Plugin Manager", hint: "recommended zinit or skip" },
{ value: "cli-tools", label: "CLI Tools", hint: "bat, eza, fzf, fd, zoxide, atuin..." },
{ value: "apps", label: "GUI Apps", hint: "Ghostty, Raycast, VS Code..." },
{ value: "frontend", label: "Frontend Tools", hint: "fnm, Node.js, pnpm, git-cz" },
{ value: "frontend", label: "Frontend Tools", hint: "fnm, Node.js, pnpm, Bun, git-cz" },
{ value: "aliases", label: "Shell Aliases", hint: "git, eza, fzf shortcuts" },
{ value: "ssh", label: "SSH Key", hint: "generate GitHub SSH key" },
{ value: "vim", label: "Vim Config", hint: "basic vim setup" },
Expand Down
16 changes: 16 additions & 0 deletions src/steps/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const FRONTEND_TOOLS = {
],
packageManagers: [
{ value: "pnpm", label: "pnpm", hint: "fast, disk-efficient package manager" },
{ value: "bun", label: "Bun", hint: "JavaScript runtime and package manager" },
],
git: [
{ value: "git-cz", label: "git-cz", hint: "Conventional Commits CLI" },
Expand Down Expand Up @@ -162,6 +163,21 @@ export async function installFrontendTools(selectedTools = getAllFrontendToolVal
}
}

// Bun
if (!wanted.has("bun")) {
// skip
} else if (commandExists("bun")) {
p.log.success("Bun is already installed");
} else {
p.log.step("Installing Bun...");
try {
await runStream("curl -fsSL https://bun.sh/install | bash");
p.log.success("Bun installed");
} catch {
p.log.warn("Could not install Bun automatically — try rerunning after checking your network connection");
Comment thread
ChangeHow marked this conversation as resolved.
}
}

// git-cz
if (!wanted.has("git-cz")) {
// skip
Expand Down
1 change: 1 addition & 0 deletions src/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const CHECKS = {
{ cmd: "fnm", label: "fnm" },
{ cmd: "node", label: "Node.js" },
{ cmd: "pnpm", label: "pnpm" },
{ cmd: "bun", label: "Bun" },
],
};

Expand Down
24 changes: 24 additions & 0 deletions tests/configs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ describe("Static config templates", () => {
expect(content).toContain(":${PATH}:");
});

test("core/paths.zsh keeps Bun on PATH for non-interactive shells", () => {
const content = readFileSync(join(CONFIGS_DIR, "core", "paths.zsh"), "utf-8");
expect(content).toContain('export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"');
expect(content).toContain('${BUN_INSTALL}/bin:${PATH}');
});

test("core/env.zsh does not contain API keys", () => {
const content = readFileSync(join(CONFIGS_DIR, "core", "env.zsh"), "utf-8");
for (const pattern of FORBIDDEN_PATTERNS) {
Expand All @@ -213,6 +219,24 @@ describe("Static config templates", () => {
expect(fzfContent).not.toContain("fzf --zsh");
});

test("shared/tools/fzf.zsh defers Ctrl-T preview paths until fzf runs", () => {
const fzfOpts = execFileSync(
"zsh",
["-f", "-c", 'source "$FZF_CONFIG"; print -r -- "$FZF_CTRL_T_OPTS"'],
{
encoding: "utf-8",
env: {
...process.env,
FZF_CONFIG: join(CONFIGS_DIR, "shared", "tools", "fzf.zsh"),
},
}
);

expect(fzfOpts).toContain('if [[ "$target" != /* ]]');
expect(fzfOpts).toContain('target="${FZF_CTRL_T_PREVIEW_ROOT:-$PWD}/$target"');
expect(fzfOpts).toContain('bat --color=always --style=plain --line-range :300 "$target"');
});

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("_load_tool_config");
Expand Down
10 changes: 10 additions & 0 deletions tests/frontend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe("frontend step", () => {
// Should NOT contain curl fnm install or npm install -g
expect(calls.some((c) => c.includes("fnm.vercel.app"))).toBe(false);
expect(calls.some((c) => c.includes("npm install -g pnpm"))).toBe(false);
expect(calls.some((c) => c.includes("bun.sh/install"))).toBe(false);
expect(calls.some((c) => c.includes("npm install -g git-cz"))).toBe(false);
});

Expand Down Expand Up @@ -129,6 +130,14 @@ describe("frontend step", () => {
);
});

test("installs Bun when not present", async () => {
commandExists.mockImplementation((name) => name !== "bun");

await installFrontendTools(["bun"], { home: sandbox });

expect(runStream).toHaveBeenCalledWith("curl -fsSL https://bun.sh/install | bash");
});

test("installs git-cz when not present", async () => {
commandExists.mockImplementation((name) => {
if (name === "git-cz") return false;
Expand Down Expand Up @@ -160,6 +169,7 @@ describe("frontend step", () => {
expect(calls.some((cmd) => cmd.includes("fnm install"))).toBe(false);
expect(calls).toContain("npm install -g git-cz");
expect(calls.some((cmd) => cmd.includes("npm install -g pnpm"))).toBe(false);
expect(calls.some((cmd) => cmd.includes("bun.sh/install"))).toBe(false);
});

test("removes legacy bootstrap node shims when fnm default node exists", async () => {
Expand Down
3 changes: 2 additions & 1 deletion tests/setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ describe("Setup simulation in sandbox", () => {
"fnm",
"node",
"pnpm",
"bun",
"git-cz",
]);
});
Expand Down Expand Up @@ -243,7 +244,7 @@ describe("Setup simulation in sandbox", () => {
expect(message).toContain("Suit up!");
});

test("detects completed suitup-managed setup steps", () => {
test("detects completed suitup-managed setup steps without optional Bun", () => {
mkdirSync(join(sandbox, ".config", "zsh", "core"), { recursive: true });
mkdirSync(join(sandbox, ".config", "zsh", "shared"), { recursive: true });
mkdirSync(join(sandbox, ".config", "zsh", "local"), { recursive: true });
Expand Down