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
63 changes: 60 additions & 3 deletions src/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { setupZshConfig, writeZshrc, writeZshenv } from "./steps/zsh-config.js";
import { readFileSafe } from "./utils/fs.js";
import { commandExists } from "./utils/shell.js";
import { isZshShell } from "./utils/shell-context.js";
import { CONFIGS_DIR } from "./constants.js";
import { isSuitupManagedConfig, mergeLineAdditions } from "./utils/config-diff.js";
export { isZshShell } from "./utils/shell-context.js";

export const SUITUP_PIXEL_LOGO = [
Expand Down Expand Up @@ -170,9 +172,59 @@ export function detectCompletedSteps({
return [...completed];
}

function hasManagedConfigAdditions(source, dest) {
if (!existsSync(dest)) {
return false;
}

const existing = readFileSafe(dest);
const shipped = readFileSafe(source);
if (existing === shipped || !isSuitupManagedConfig(existing)) {
return false;
}

return mergeLineAdditions(existing, shipped) !== existing;
}

export function detectPendingStepUpdates({ home = homedir() } = {}) {
const pending = new Set();
const zshConfigDir = join(home, ".config", "zsh");
const zshConfigFiles = [
[join(CONFIGS_DIR, "zshrc.template"), join(home, ".zshrc")],
[join(CONFIGS_DIR, "zshenv.template"), join(home, ".zshenv")],
...["perf.zsh", "env.zsh", "paths.zsh", "options.zsh"].map((file) => [
join(CONFIGS_DIR, "core", file),
join(zshConfigDir, "core", file),
]),
...["tools.zsh", "completion.zsh", "highlighting.zsh"].map((file) => [
join(CONFIGS_DIR, "shared", file),
join(zshConfigDir, "shared", file),
]),
...["_loader.zsh", "fzf.zsh", "runtime.zsh", "atuin.zsh", "bun.zsh"].map((file) => [
join(CONFIGS_DIR, "shared", "tools", file),
join(zshConfigDir, "shared", "tools", file),
]),
];

if (zshConfigFiles.some(([source, dest]) => hasManagedConfigAdditions(source, dest))) {
pending.add("zsh-config");
}

if (hasManagedConfigAdditions(join(CONFIGS_DIR, "shared", "plugins.zsh"), join(zshConfigDir, "shared", "plugins.zsh"))) {
pending.add("plugins");
}

if (hasManagedConfigAdditions(join(CONFIGS_DIR, "shared", "aliases.zsh"), join(zshConfigDir, "shared", "aliases.zsh"))) {
pending.add("aliases");
}

return [...pending];
}

export function getInitialStepValues(opts = {}) {
const completed = new Set(detectCompletedSteps(opts));
return getDefaultSteps(opts.platform).filter((step) => !completed.has(step));
const pendingUpdates = new Set(detectPendingStepUpdates(opts));
return getDefaultSteps(opts.platform).filter((step) => !completed.has(step) || pendingUpdates.has(step));
}

export async function runSetup({ defaults = false } = {}) {
Expand All @@ -188,8 +240,13 @@ export async function runSetup({ defaults = false } = {}) {
// --- Step 1: Select setup steps ---
const completedSteps = detectCompletedSteps();
const initialValues = getInitialStepValues();
if (completedSteps.length > 0) {
p.log.info(`Deselected already configured steps: ${completedSteps.join(", ")}`);
const pendingUpdates = detectPendingStepUpdates();
const deselectedSteps = completedSteps.filter((step) => !initialValues.includes(step));
if (deselectedSteps.length > 0) {
p.log.info(`Deselected already configured steps: ${deselectedSteps.join(", ")}`);
}
if (pendingUpdates.length > 0) {
p.log.info(`Preselected steps with suitup config additions: ${pendingUpdates.join(", ")}`);
}

const steps = defaults
Expand Down
66 changes: 56 additions & 10 deletions tests/setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { tmpdir } from "node:os";
import { createSandbox } from "./helpers.js";
import {
detectCompletedSteps,
detectPendingStepUpdates,
getDefaultSteps,
getInitialStepValues,
getRecommendedAppValues,
Expand Down Expand Up @@ -283,16 +284,17 @@ describe("Setup simulation in sandbox", () => {
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");
writeFileSync(join(completedSandbox.path, ".config", "zsh", "core", "perf.zsh"), "", "utf-8");
writeFileSync(join(completedSandbox.path, ".config", "zsh", "core", "env.zsh"), "", "utf-8");
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", "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");
copyFileSync(join(CONFIGS_DIR, "zshrc.template"), join(completedSandbox.path, ".zshrc"));
copyFileSync(join(CONFIGS_DIR, "zshenv.template"), join(completedSandbox.path, ".zshenv"));
for (const file of ["perf.zsh", "env.zsh", "paths.zsh", "options.zsh"]) {
copyFileSync(join(CONFIGS_DIR, "core", file), join(completedSandbox.path, ".config", "zsh", "core", file));
}
for (const file of ["tools.zsh", "completion.zsh", "highlighting.zsh"]) {
copyFileSync(join(CONFIGS_DIR, "shared", file), join(completedSandbox.path, ".config", "zsh", "shared", file));
}
for (const file of ["_loader.zsh", "fzf.zsh", "runtime.zsh", "atuin.zsh", "bun.zsh"]) {
copyFileSync(join(CONFIGS_DIR, "shared", "tools", file), join(completedSandbox.path, ".config", "zsh", "shared", "tools", file));
}
writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "prompt.zsh"), "", "utf-8");
writeFileSync(join(completedSandbox.path, ".config", "zsh", "local", "machine.zsh"), "", "utf-8");
writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "aliases.zsh"), "", "utf-8");
Expand All @@ -314,6 +316,50 @@ describe("Setup simulation in sandbox", () => {
}
});

test("keeps completed zsh config selected when managed config additions are pending", () => {
const completedSandbox = createSandbox();
try {
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 });

copyFileSync(join(CONFIGS_DIR, "zshrc.template"), join(completedSandbox.path, ".zshrc"));
copyFileSync(join(CONFIGS_DIR, "zshenv.template"), join(completedSandbox.path, ".zshenv"));
for (const file of ["perf.zsh", "env.zsh", "paths.zsh", "options.zsh"]) {
copyFileSync(join(CONFIGS_DIR, "core", file), join(completedSandbox.path, ".config", "zsh", "core", file));
}
for (const file of ["tools.zsh", "highlighting.zsh"]) {
copyFileSync(join(CONFIGS_DIR, "shared", file), join(completedSandbox.path, ".config", "zsh", "shared", file));
}
for (const file of ["_loader.zsh", "fzf.zsh", "runtime.zsh", "atuin.zsh", "bun.zsh"]) {
copyFileSync(join(CONFIGS_DIR, "shared", "tools", file), join(completedSandbox.path, ".config", "zsh", "shared", "tools", file));
}
writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "prompt.zsh"), "", "utf-8");
writeFileSync(join(completedSandbox.path, ".config", "zsh", "local", "machine.zsh"), "", "utf-8");
writeFileSync(join(completedSandbox.path, ".config", "zsh", "shared", "aliases.zsh"), "", "utf-8");
writeFileSync(
join(completedSandbox.path, ".config", "zsh", "shared", "completion.zsh"),
"# ============================================================================\n# Native zsh completion\n# Generated by suitup\n# ============================================================================\n\nautoload -Uz compinit\ncompinit\n",
"utf-8"
);

const opts = {
home: completedSandbox.path,
platform: "darwin",
commandExistsFn(name) {
return ["zsh", "brew"].includes(name);
},
};

expect(detectCompletedSteps(opts)).toContain("zsh-config");
expect(detectPendingStepUpdates(opts)).toContain("zsh-config");
expect(getInitialStepValues(opts)).toContain("zsh-config");
} finally {
completedSandbox.cleanup();
}
});

test("does not treat non-suitup shell files as completed zsh config", () => {
mkdirSync(join(sandbox, ".config", "zsh", "core"), { recursive: true });
mkdirSync(join(sandbox, ".config", "zsh", "shared"), { recursive: true });
Expand Down
Loading