From 2b0f8d9f0d9047373222c8a4b71238e05f2e9cf5 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Thu, 11 Dec 2025 15:13:15 -0800 Subject: [PATCH 1/5] Skeleton --- packages/safe-chain/bin/safe-chain.js | 3 +- .../src/shell-integration/helpers.js | 7 ++++ .../src/shell-integration/setup-ci.js | 4 +- .../src/shell-integration/setup-ci.spec.js | 1 + .../src/shell-integration/teardown.js | 24 ++++++++++- test/e2e/teardown-ci.e2e.spec.js | 41 +++++++++++++++++++ 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 test/e2e/teardown-ci.e2e.spec.js diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 2793987..ad43104 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -3,7 +3,7 @@ import chalk from "chalk"; import { ui } from "../src/environment/userInteraction.js"; import { setup } from "../src/shell-integration/setup.js"; -import { teardown } from "../src/shell-integration/teardown.js"; +import { teardown, teardownCi } from "../src/shell-integration/teardown.js"; import { setupCi } from "../src/shell-integration/setup-ci.js"; import { initializeCliArguments } from "../src/config/cliArguments.js"; import { setEcoSystem } from "../src/config/settings.js"; @@ -61,6 +61,7 @@ if (tool) { setup(); } else if (command === "teardown") { teardown(); + teardownCi(); } else if (command === "setup-ci") { setupCi(); } else if (command === "--version" || command === "-v" || command === "-v") { diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 50cea5d..844b48e 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -113,6 +113,13 @@ export function getPackageManagerList() { return `${tools.join(", ")}, and ${lastTool} commands`; } +/** + * @returns {string} + */ +export function getShimsDir() { + return path.join(os.homedir(), ".safe-chain", "shims"); +} + /** * @param {string} executableName * diff --git a/packages/safe-chain/src/shell-integration/setup-ci.js b/packages/safe-chain/src/shell-integration/setup-ci.js index bc5c5e6..b0a8c83 100644 --- a/packages/safe-chain/src/shell-integration/setup-ci.js +++ b/packages/safe-chain/src/shell-integration/setup-ci.js @@ -1,6 +1,6 @@ import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; -import { getPackageManagerList, knownAikidoTools } from "./helpers.js"; +import { getPackageManagerList, knownAikidoTools, getShimsDir } from "./helpers.js"; import fs from "fs"; import os from "os"; import path from "path"; @@ -32,7 +32,7 @@ export async function setupCi() { ); ui.emptyLine(); - const shimsDir = path.join(os.homedir(), ".safe-chain", "shims"); + const shimsDir = getShimsDir(); const binDir = path.join(os.homedir(), ".safe-chain", "bin"); // Create the shims directory if it doesn't exist if (!fs.existsSync(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 92ef82e..b437157 100644 --- a/packages/safe-chain/src/shell-integration/setup-ci.spec.js +++ b/packages/safe-chain/src/shell-integration/setup-ci.spec.js @@ -50,6 +50,7 @@ describe("Setup CI shell integration", () => { { tool: "yarn", aikidoCommand: "aikido-yarn" }, ], getPackageManagerList: () => "npm, yarn", + getShimsDir: () => mockShimsDir, }, }); diff --git a/packages/safe-chain/src/shell-integration/teardown.js b/packages/safe-chain/src/shell-integration/teardown.js index bc83b48..f5f86a9 100644 --- a/packages/safe-chain/src/shell-integration/teardown.js +++ b/packages/safe-chain/src/shell-integration/teardown.js @@ -1,7 +1,8 @@ import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; import { detectShells } from "./shellDetection.js"; -import { knownAikidoTools, getPackageManagerList } from "./helpers.js"; +import { knownAikidoTools, getPackageManagerList, getShimsDir, } from "./helpers.js"; +import fs from "fs"; /** * @returns {Promise} @@ -62,3 +63,24 @@ export async function teardown() { return; } } + +/** + * @returns {Promise} + */ +export async function teardownCi() { + const shimsDir = getShimsDir(); + if (fs.existsSync(shimsDir)) { + try { + fs.rmSync(shimsDir, { recursive: true, force: true }); + ui.writeInformation( + `${chalk.bold("- CI Shims:")} ${chalk.green("Removed successfully")}` + ); + } catch (/** @type {any} */ error) { + ui.writeError( + `${chalk.bold("- CI Shims:")} ${chalk.red( + "Failed to remove" + )}. Error: ${error.message}` + ); + } + } +} diff --git a/test/e2e/teardown-ci.e2e.spec.js b/test/e2e/teardown-ci.e2e.spec.js new file mode 100644 index 0000000..fe97d5e --- /dev/null +++ b/test/e2e/teardown-ci.e2e.spec.js @@ -0,0 +1,41 @@ +import { describe, it, before, beforeEach, afterEach } from "node:test"; +import { DockerTestContainer } from "./DockerTestContainer.js"; +import assert from "node:assert"; + +describe("E2E: safe-chain teardown command (CI)", () => { + let container; + + before(async () => { + DockerTestContainer.buildImage(); + }); + + beforeEach(async () => { + container = new DockerTestContainer(); + await container.start(); + }); + + afterEach(async () => { + if (container) { + await container.stop(); + container = null; + } + }); + + it("safe-chain teardown removes shims directory created by setup-ci", async () => { + const shell = await container.openShell("bash"); + + // Run setup-ci + await shell.runCommand("safe-chain setup-ci"); + + // Verify shims directory exists + const checkShimsExist = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'"); + assert.ok(checkShimsExist.output.includes("exists"), "Shims directory should exist after setup-ci"); + + // Run teardown + await shell.runCommand("safe-chain teardown"); + + // Verify shims directory is gone + const checkShimsGone = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'"); + assert.ok(checkShimsGone.output.includes("missing"), "Shims directory should be removed after teardown"); + }); +}); From 092df576959a31943d334a09ca5ec5715215699c Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Thu, 11 Dec 2025 20:29:58 -0800 Subject: [PATCH 2/5] Change order --- packages/safe-chain/bin/safe-chain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index ad43104..36898a9 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -60,8 +60,8 @@ if (tool) { } else if (command === "setup") { setup(); } else if (command === "teardown") { - teardown(); teardownCi(); + teardown(); } else if (command === "setup-ci") { setupCi(); } else if (command === "--version" || command === "-v" || command === "-v") { From a405a517063c92fd5849bd9087472d4c5477c2b0 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Fri, 12 Dec 2025 11:17:17 -0800 Subject: [PATCH 3/5] Also remove script dir --- packages/safe-chain/bin/safe-chain.js | 4 ++-- .../src/shell-integration/helpers.js | 7 ++++++ .../safe-chain/src/shell-integration/setup.js | 6 ++--- .../src/shell-integration/teardown.js | 24 +++++++++++++++++-- ....e2e.spec.js => teardown-dirs.e2e.spec.js} | 20 +++++++++++++++- 5 files changed, 53 insertions(+), 8 deletions(-) rename test/e2e/{teardown-ci.e2e.spec.js => teardown-dirs.e2e.spec.js} (59%) diff --git a/packages/safe-chain/bin/safe-chain.js b/packages/safe-chain/bin/safe-chain.js index 36898a9..802005b 100755 --- a/packages/safe-chain/bin/safe-chain.js +++ b/packages/safe-chain/bin/safe-chain.js @@ -3,7 +3,7 @@ import chalk from "chalk"; import { ui } from "../src/environment/userInteraction.js"; import { setup } from "../src/shell-integration/setup.js"; -import { teardown, teardownCi } from "../src/shell-integration/teardown.js"; +import { teardown, teardownDirectories } from "../src/shell-integration/teardown.js"; import { setupCi } from "../src/shell-integration/setup-ci.js"; import { initializeCliArguments } from "../src/config/cliArguments.js"; import { setEcoSystem } from "../src/config/settings.js"; @@ -60,7 +60,7 @@ if (tool) { } else if (command === "setup") { setup(); } else if (command === "teardown") { - teardownCi(); + teardownDirectories(); teardown(); } else if (command === "setup-ci") { setupCi(); diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 844b48e..3b08bf2 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -120,6 +120,13 @@ export function getShimsDir() { return path.join(os.homedir(), ".safe-chain", "shims"); } +/** + * @returns {string} + */ +export function getScriptsDir() { + return path.join(os.homedir(), ".safe-chain", "scripts"); +} + /** * @param {string} executableName * diff --git a/packages/safe-chain/src/shell-integration/setup.js b/packages/safe-chain/src/shell-integration/setup.js index d5c4be9..94eb4fb 100644 --- a/packages/safe-chain/src/shell-integration/setup.js +++ b/packages/safe-chain/src/shell-integration/setup.js @@ -1,7 +1,7 @@ import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; import { detectShells } from "./shellDetection.js"; -import { knownAikidoTools, getPackageManagerList } from "./helpers.js"; +import { knownAikidoTools, getPackageManagerList, getScriptsDir } from "./helpers.js"; import fs from "fs"; import os from "os"; import path from "path"; @@ -107,10 +107,10 @@ function setupShell(shell) { function copyStartupFiles() { const startupFiles = ["init-posix.sh", "init-pwsh.ps1", "init-fish.fish"]; + const targetDir = getScriptsDir(); for (const file of startupFiles) { - const targetDir = path.join(os.homedir(), ".safe-chain", "scripts"); - const targetPath = path.join(os.homedir(), ".safe-chain", "scripts", file); + const targetPath = path.join(targetDir, file); if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); diff --git a/packages/safe-chain/src/shell-integration/teardown.js b/packages/safe-chain/src/shell-integration/teardown.js index f5f86a9..de3fbd7 100644 --- a/packages/safe-chain/src/shell-integration/teardown.js +++ b/packages/safe-chain/src/shell-integration/teardown.js @@ -1,7 +1,7 @@ import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; import { detectShells } from "./shellDetection.js"; -import { knownAikidoTools, getPackageManagerList, getShimsDir, } from "./helpers.js"; +import { knownAikidoTools, getPackageManagerList, getShimsDir, getScriptsDir } from "./helpers.js"; import fs from "fs"; /** @@ -65,10 +65,14 @@ export async function teardown() { } /** + * Removes directories created by setup-ci and setup commands * @returns {Promise} */ -export async function teardownCi() { +export async function teardownDirectories() { const shimsDir = getShimsDir(); + const scriptsDir = getScriptsDir(); + + // Remove CI shims directory if (fs.existsSync(shimsDir)) { try { fs.rmSync(shimsDir, { recursive: true, force: true }); @@ -83,4 +87,20 @@ export async function teardownCi() { ); } } + + // Remove scripts directory + if (fs.existsSync(scriptsDir)) { + try { + fs.rmSync(scriptsDir, { recursive: true, force: true }); + ui.writeInformation( + `${chalk.bold("- Scripts:")} ${chalk.green("Removed successfully")}` + ); + } catch (/** @type {any} */ error) { + ui.writeError( + `${chalk.bold("- Scripts:")} ${chalk.red( + "Failed to remove" + )}. Error: ${error.message}` + ); + } + } } diff --git a/test/e2e/teardown-ci.e2e.spec.js b/test/e2e/teardown-dirs.e2e.spec.js similarity index 59% rename from test/e2e/teardown-ci.e2e.spec.js rename to test/e2e/teardown-dirs.e2e.spec.js index fe97d5e..912355f 100644 --- a/test/e2e/teardown-ci.e2e.spec.js +++ b/test/e2e/teardown-dirs.e2e.spec.js @@ -2,7 +2,7 @@ import { describe, it, before, beforeEach, afterEach } from "node:test"; import { DockerTestContainer } from "./DockerTestContainer.js"; import assert from "node:assert"; -describe("E2E: safe-chain teardown command (CI)", () => { +describe("E2E: safe-chain teardown command", () => { let container; before(async () => { @@ -38,4 +38,22 @@ describe("E2E: safe-chain teardown command (CI)", () => { const checkShimsGone = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'"); assert.ok(checkShimsGone.output.includes("missing"), "Shims directory should be removed after teardown"); }); + + it("safe-chain teardown removes scripts directory created by setup", async () => { + const shell = await container.openShell("bash"); + + // Run setup + await shell.runCommand("safe-chain setup"); + + // Verify scripts directory exists + const checkScriptsExist = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'"); + assert.ok(checkScriptsExist.output.includes("exists"), "Scripts directory should exist after setup"); + + // Run teardown + await shell.runCommand("safe-chain teardown"); + + // Verify scripts directory is gone + const checkScriptsGone = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'"); + assert.ok(checkScriptsGone.output.includes("missing"), "Scripts directory should be removed after teardown"); + }); }); From 68180e5b440b8fa6c7459414f6983d3c57992e19 Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Fri, 12 Dec 2025 11:26:53 -0800 Subject: [PATCH 4/5] Add more tests --- test/e2e/teardown-dirs.e2e.spec.js | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/e2e/teardown-dirs.e2e.spec.js b/test/e2e/teardown-dirs.e2e.spec.js index 912355f..0ed8bf6 100644 --- a/test/e2e/teardown-dirs.e2e.spec.js +++ b/test/e2e/teardown-dirs.e2e.spec.js @@ -56,4 +56,44 @@ describe("E2E: safe-chain teardown command", () => { const checkScriptsGone = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'"); assert.ok(checkScriptsGone.output.includes("missing"), "Scripts directory should be removed after teardown"); }); + + it("safe-chain teardown removes shims directory created by setup-ci --include-python", async () => { + const shell = await container.openShell("bash"); + + // Run setup-ci with --include-python + await shell.runCommand("safe-chain setup-ci --include-python"); + + // Verify shims directory exists + const checkShimsExist = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'"); + assert.ok(checkShimsExist.output.includes("exists"), "Shims directory should exist after setup-ci --include-python"); + + // Verify Python shims were created + const checkPythonShims = await shell.runCommand("test -f ~/.safe-chain/shims/pip && echo 'exists' || echo 'missing'"); + assert.ok(checkPythonShims.output.includes("exists"), "Python shims should exist after setup-ci --include-python"); + + // Run teardown + await shell.runCommand("safe-chain teardown"); + + // Verify shims directory is gone + const checkShimsGone = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'"); + assert.ok(checkShimsGone.output.includes("missing"), "Shims directory should be removed after teardown"); + }); + + it("safe-chain teardown removes scripts directory created by setup --include-python", async () => { + const shell = await container.openShell("bash"); + + // Run setup with --include-python + await shell.runCommand("safe-chain setup --include-python"); + + // Verify scripts directory exists + const checkScriptsExist = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'"); + assert.ok(checkScriptsExist.output.includes("exists"), "Scripts directory should exist after setup --include-python"); + + // Run teardown + await shell.runCommand("safe-chain teardown"); + + // Verify scripts directory is gone + const checkScriptsGone = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'"); + assert.ok(checkScriptsGone.output.includes("missing"), "Scripts directory should be removed after teardown"); + }); }); From f47cd7ebc099c934e3a4fff8a6abdd15ff829dfa Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Fri, 12 Dec 2025 12:07:06 -0800 Subject: [PATCH 5/5] Remove unused import --- packages/safe-chain/src/shell-integration/setup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/safe-chain/src/shell-integration/setup.js b/packages/safe-chain/src/shell-integration/setup.js index 94eb4fb..065de75 100644 --- a/packages/safe-chain/src/shell-integration/setup.js +++ b/packages/safe-chain/src/shell-integration/setup.js @@ -3,7 +3,6 @@ import { ui } from "../environment/userInteraction.js"; import { detectShells } from "./shellDetection.js"; import { knownAikidoTools, getPackageManagerList, getScriptsDir } from "./helpers.js"; import fs from "fs"; -import os from "os"; import path from "path"; import { includePython } from "../config/cliArguments.js"; import { fileURLToPath } from "url";