diff --git a/packages/safe-chain/src/shell-integration/setup-ci.js b/packages/safe-chain/src/shell-integration/setup-ci.js index 762bd9bc..b1de8901 100644 --- a/packages/safe-chain/src/shell-integration/setup-ci.js +++ b/packages/safe-chain/src/shell-integration/setup-ci.js @@ -1,6 +1,7 @@ import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; import { getPackageManagerList, knownAikidoTools, getShimsDir } from "./helpers.js"; +import { detectShells } from "./shellDetection.js"; import fs from "fs"; import os from "os"; import path from "path"; @@ -37,12 +38,34 @@ export async function setupCi() { fs.mkdirSync(shimsDir, { recursive: true }); } + cleanupLegacyShellInit(); createShims(shimsDir); ui.writeInformation(`Created shims in ${shimsDir}`); modifyPathForCi(shimsDir, binDir); ui.writeInformation(`Added shims directory to PATH for CI environments.`); } +/** + * Removes shell-based initialization from RC files when switching to --ci install mode. + */ +function cleanupLegacyShellInit() { + let shells; + try { + shells = detectShells(); + } catch { + // Best-effort cleanup — skip if shell detection fails + return; + } + + for (const shell of shells) { + try { + shell.teardown(knownAikidoTools); + } catch { + // Best-effort cleanup — don't fail the install if teardown errors + } + } +} + /** * @param {string} shimsDir * diff --git a/packages/safe-chain/src/shell-integration/setup-ci.spec.js b/packages/safe-chain/src/shell-integration/setup-ci.spec.js index b4371573..e2edf75c 100644 --- a/packages/safe-chain/src/shell-integration/setup-ci.spec.js +++ b/packages/safe-chain/src/shell-integration/setup-ci.spec.js @@ -42,6 +42,13 @@ describe("Setup CI shell integration", () => { }, }); + // Mock shell detection to avoid touching real RC files during tests + mock.module("./shellDetection.js", { + namedExports: { + detectShells: () => [], + }, + }); + // Mock the helpers module mock.module("./helpers.js", { namedExports: { diff --git a/test/e2e/setup-ci.e2e.spec.js b/test/e2e/setup-ci.e2e.spec.js index 7237b1a1..be60e750 100644 --- a/test/e2e/setup-ci.e2e.spec.js +++ b/test/e2e/setup-ci.e2e.spec.js @@ -53,6 +53,46 @@ describe("E2E: safe-chain setup-ci command", () => { }); } + for (let shell of ["bash", "zsh"]) { + it(`safe-chain setup-ci removes legacy shell init left by a previous setup for ${shell}`, async () => { + const rcFile = shell === "zsh" ? "~/.zshrc" : "~/.bashrc"; + + // Simulate a previous non-CI install + const installationShell = await container.openShell(shell); + await installationShell.runCommand("safe-chain setup"); + + // Verify the legacy init line was added + const afterSetup = await installationShell.runCommand(`grep -c "init-posix.sh" ${rcFile} || true`); + assert.ok( + afterSetup.output.includes("1"), + `Expected init-posix.sh to be present in ${rcFile} after setup` + ); + + // Now run the CI install — should clean up the legacy line + await installationShell.runCommand("safe-chain setup-ci"); + await installationShell.runCommand( + `echo 'export PATH="$HOME/.safe-chain/shims:$PATH"' >> ${rcFile}` + ); + + const afterSetupCi = await installationShell.runCommand(`grep -c "init-posix.sh" ${rcFile} || true`); + assert.ok( + !afterSetupCi.output.includes("1"), + `Expected init-posix.sh to be removed from ${rcFile} after setup-ci, but it was still present` + ); + + // Verify npm still works through shims in a new shell + const projectShell = await container.openShell(shell); + const result = await projectShell.runCommand( + "npm i axios@1.13.0 --safe-chain-logging=verbose" + ); + + assert.ok( + result.output.includes("Safe-chain: Scanned"), + `Expected npm to be wrapped by safe-chain shim after setup-ci.\nOutput was:\n${result.output}` + ); + }); + } + it("writes to GITHUB_PATH when GITHUB_PATH is set", async () => { const installationShell = await container.openShell("zsh"); await installationShell.runCommand("export GITHUB_PATH=/tmp/github_path");