diff --git a/packages/safe-chain/src/registryProxy/certUtils.js b/packages/safe-chain/src/registryProxy/certUtils.js index 3c8790c..a4bc0b1 100644 --- a/packages/safe-chain/src/registryProxy/certUtils.js +++ b/packages/safe-chain/src/registryProxy/certUtils.js @@ -2,12 +2,17 @@ import forge from "node-forge"; import path from "path"; import fs from "fs"; import os from "os"; +import { getSafeChainDir } from "../config/environmentVariables.js"; -const certFolder = path.join(os.homedir(), ".safe-chain", "certs"); const ca = loadCa(); const certCache = new Map(); +function getCertFolder() { + const safeChainDir = getSafeChainDir() ?? path.join(os.homedir(), ".safe-chain"); + return path.join(safeChainDir, "certs"); +} + /** * @param {forge.pki.PublicKey} publicKey * @returns {string} @@ -20,7 +25,7 @@ function createKeyIdentifier(publicKey) { } export function getCaCertPath() { - return path.join(certFolder, "ca-cert.pem"); + return path.join(getCertFolder(), "ca-cert.pem"); } /** @@ -112,6 +117,7 @@ export function generateCertForHost(hostname) { } function loadCa() { + const certFolder = getCertFolder(); const keyPath = path.join(certFolder, "ca-key.pem"); const certPath = path.join(certFolder, "ca-cert.pem"); diff --git a/packages/safe-chain/src/registryProxy/certUtils.spec.js b/packages/safe-chain/src/registryProxy/certUtils.spec.js new file mode 100644 index 0000000..ebf8dab --- /dev/null +++ b/packages/safe-chain/src/registryProxy/certUtils.spec.js @@ -0,0 +1,71 @@ +import { describe, it, beforeEach, afterEach, mock } from "node:test"; +import assert from "node:assert"; + +describe("certUtils", () => { + let originalSafeChainDir; + + beforeEach(() => { + originalSafeChainDir = process.env.SAFE_CHAIN_DIR; + }); + + afterEach(() => { + if (originalSafeChainDir === undefined) { + delete process.env.SAFE_CHAIN_DIR; + } else { + process.env.SAFE_CHAIN_DIR = originalSafeChainDir; + } + + mock.reset(); + }); + + it("stores CA certificates in SAFE_CHAIN_DIR when configured", async () => { + process.env.SAFE_CHAIN_DIR = "/custom/safe-chain"; + + mock.module("fs", { + defaultExport: { + existsSync: () => false, + mkdirSync: () => {}, + writeFileSync: () => {}, + }, + }); + + mock.module("node-forge", { + defaultExport: { + pki: { + getPublicKeyFingerprint: () => "fingerprint", + rsa: { + generateKeyPair: () => ({ + publicKey: "public-key", + privateKey: "private-key", + }), + }, + createCertificate: () => ({ + publicKey: null, + serialNumber: "", + validity: { + notBefore: new Date(), + notAfter: new Date(), + }, + setSubject: () => {}, + setIssuer: () => {}, + setExtensions: () => {}, + sign: () => {}, + }), + privateKeyToPem: () => "private-key-pem", + certificateToPem: () => "certificate-pem", + }, + md: { + sha1: { create: () => "sha1" }, + sha256: { create: () => "sha256" }, + }, + }, + }); + + const { getCaCertPath } = await import("./certUtils.js"); + + assert.strictEqual( + getCaCertPath(), + "/custom/safe-chain/certs/ca-cert.pem", + ); + }); +}); diff --git a/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd b/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd index 082d553..959b700 100644 --- a/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +++ b/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd @@ -3,7 +3,11 @@ REM Generated wrapper for {{PACKAGE_MANAGER}} by safe-chain REM This wrapper intercepts {{PACKAGE_MANAGER}} calls for non-interactive environments REM Remove shim directory from PATH to prevent infinite loops -set "SHIM_DIR=%USERPROFILE%\.safe-chain\shims" +if defined SAFE_CHAIN_DIR ( + set "SHIM_DIR=%SAFE_CHAIN_DIR%\shims" +) else ( + set "SHIM_DIR=%USERPROFILE%\.safe-chain\shims" +) call set "CLEAN_PATH=%%PATH:%SHIM_DIR%;=%%" REM Check if aikido command is available with clean PATH @@ -21,4 +25,4 @@ if %errorlevel%==0 ( REM If we get here, original command was not found echo Error: Could not find original {{PACKAGE_MANAGER}} >&2 exit /b 1 -) \ No newline at end of file +) 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 c0a5ca1..1156173 100644 --- a/packages/safe-chain/src/shell-integration/setup-ci.spec.js +++ b/packages/safe-chain/src/shell-integration/setup-ci.spec.js @@ -27,7 +27,7 @@ describe("Setup CI shell integration", () => { ); fs.writeFileSync( path.join(mockTemplateDir, "path-wrappers", "templates", "windows-wrapper.template.cmd"), - "@echo off\nREM Template for {{PACKAGE_MANAGER}}\n{{AIKIDO_COMMAND}} %*\n", + "@echo off\nif defined SAFE_CHAIN_DIR (\n set \"SHIM_DIR=%SAFE_CHAIN_DIR%\\shims\"\n) else (\n set \"SHIM_DIR=%USERPROFILE%\\.safe-chain\\shims\"\n)\n{{AIKIDO_COMMAND}} %*\n", "utf-8" ); @@ -143,6 +143,10 @@ describe("Setup CI shell integration", () => { assert.ok(npmShimContent.includes("aikido-npm"), "npm.cmd should contain aikido-npm"); assert.ok(npmShimContent.includes("@echo off"), "npm.cmd should have Windows batch header"); assert.ok(npmShimContent.includes("%*"), "npm.cmd should use Windows argument passing"); + assert.ok( + npmShimContent.includes("if defined SAFE_CHAIN_DIR"), + "npm.cmd should honor SAFE_CHAIN_DIR when removing shim dir from PATH", + ); // Verify Unix shims were NOT created const unixNpmShim = path.join(mockShimsDir, "npm"); diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.js index 4f04c5e..bcf0bc6 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.js @@ -142,19 +142,37 @@ function cygpathw(path) { } function getManualTeardownInstructions() { - return [ - `Remove the following line from your ~/.bashrc file:`, - ` source ~/.safe-chain/scripts/init-posix.sh`, - `Then restart your terminal or run: source ~/.bashrc`, - ]; + const customDir = getSafeChainDir(); + const instructions = [`Remove the following line from your ~/.bashrc file:`]; + + if (customDir) { + instructions.push( + ` export SAFE_CHAIN_DIR="${customDir}"`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + ); + } else { + instructions.push(` source ~/.safe-chain/scripts/init-posix.sh`); + } + + instructions.push(`Then restart your terminal or run: source ~/.bashrc`); + return instructions; } function getManualSetupInstructions() { - return [ - `Add the following line to your ~/.bashrc file:`, - ` source ~/.safe-chain/scripts/init-posix.sh`, - `Then restart your terminal or run: source ~/.bashrc`, - ]; + const customDir = getSafeChainDir(); + const instructions = [`Add the following line to your ~/.bashrc file:`]; + + if (customDir) { + instructions.push( + ` export SAFE_CHAIN_DIR="${customDir}"`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + ); + } else { + instructions.push(` source ~/.safe-chain/scripts/init-posix.sh`); + } + + instructions.push(`Then restart your terminal or run: source ~/.bashrc`); + return instructions; } /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js index a8cd067..4b25d4b 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js @@ -235,6 +235,17 @@ describe("Bash shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok(!content.includes("SAFE_CHAIN_DIR")); }); + + it("should show custom manual setup instructions when custom dir is set", () => { + getSafeChainDirResult = "/custom/safe-chain"; + + assert.deepStrictEqual(bash.getManualSetupInstructions(), [ + "Add the following line to your ~/.bashrc file:", + ' export SAFE_CHAIN_DIR="/custom/safe-chain"', + " source /test-home/.safe-chain/scripts/init-posix.sh", + "Then restart your terminal or run: source ~/.bashrc", + ]); + }); }); describe("integration tests", () => { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/fish.js b/packages/safe-chain/src/shell-integration/supported-shells/fish.js index bac8e7b..33aa48c 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/fish.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/fish.js @@ -85,19 +85,45 @@ function getStartupFile() { } function getManualTeardownInstructions() { - return [ + const customDir = getSafeChainDir(); + const instructions = [ `Remove the following line from your ~/.config/fish/config.fish file:`, - ` source ~/.safe-chain/scripts/init-fish.fish`, - `Then restart your terminal or run: source ~/.config/fish/config.fish`, ]; + + if (customDir) { + instructions.push( + ` set -gx SAFE_CHAIN_DIR "${customDir}"`, + ` source ${path.join(getScriptsDir(), "init-fish.fish")}`, + ); + } else { + instructions.push(` source ~/.safe-chain/scripts/init-fish.fish`); + } + + instructions.push( + `Then restart your terminal or run: source ~/.config/fish/config.fish`, + ); + return instructions; } function getManualSetupInstructions() { - return [ + const customDir = getSafeChainDir(); + const instructions = [ `Add the following line to your ~/.config/fish/config.fish file:`, - ` source ~/.safe-chain/scripts/init-fish.fish`, - `Then restart your terminal or run: source ~/.config/fish/config.fish`, ]; + + if (customDir) { + instructions.push( + ` set -gx SAFE_CHAIN_DIR "${customDir}"`, + ` source ${path.join(getScriptsDir(), "init-fish.fish")}`, + ); + } else { + instructions.push(` source ~/.safe-chain/scripts/init-fish.fish`); + } + + instructions.push( + `Then restart your terminal or run: source ~/.config/fish/config.fish`, + ); + return instructions; } /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js index c9918c5..29b6d6e 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/fish.spec.js @@ -187,6 +187,17 @@ describe("Fish shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok(!content.includes("SAFE_CHAIN_DIR")); }); + + it("should show custom manual setup instructions when custom dir is set", () => { + getSafeChainDirResult = "/custom/safe-chain"; + + assert.deepStrictEqual(fish.getManualSetupInstructions(), [ + "Add the following line to your ~/.config/fish/config.fish file:", + ' set -gx SAFE_CHAIN_DIR "/custom/safe-chain"', + " source /test-home/.safe-chain/scripts/init-fish.fish", + "Then restart your terminal or run: source ~/.config/fish/config.fish", + ]); + }); }); describe("integration tests", () => { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js index 38b0b42..44fbfe9 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.js @@ -88,19 +88,41 @@ function getStartupFile() { } function getManualTeardownInstructions() { - return [ + const customDir = getSafeChainDir(); + const instructions = [ `Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`, - ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, - `Then restart your terminal or run: . $PROFILE`, ]; + + if (customDir) { + instructions.push( + ` $env:SAFE_CHAIN_DIR = '${customDir}'`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + ); + } else { + instructions.push(` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`); + } + + instructions.push(`Then restart your terminal or run: . $PROFILE`); + return instructions; } function getManualSetupInstructions() { - return [ + const customDir = getSafeChainDir(); + const instructions = [ `Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`, - ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, - `Then restart your terminal or run: . $PROFILE`, ]; + + if (customDir) { + instructions.push( + ` $env:SAFE_CHAIN_DIR = '${customDir}'`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + ); + } else { + instructions.push(` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`); + } + + instructions.push(`Then restart your terminal or run: . $PROFILE`); + return instructions; } /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js index 97901f1..296abfa 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/powershell.spec.js @@ -241,6 +241,17 @@ describe("PowerShell Core shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok(!content.includes("SAFE_CHAIN_DIR")); }); + + it("should show custom manual setup instructions when custom dir is set", () => { + getSafeChainDirResult = "C:\\custom\\safe-chain"; + + assert.deepStrictEqual(powershell.getManualSetupInstructions(), [ + 'Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):', + " $env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain'", + ' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"', + "Then restart your terminal or run: . $PROFILE", + ]); + }); }); describe("execution policy", () => { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js index 506b891..e3ed236 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.js @@ -88,19 +88,41 @@ function getStartupFile() { } function getManualTeardownInstructions() { - return [ + const customDir = getSafeChainDir(); + const instructions = [ `Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):`, - ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, - `Then restart your terminal or run: . $PROFILE`, ]; + + if (customDir) { + instructions.push( + ` $env:SAFE_CHAIN_DIR = '${customDir}'`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + ); + } else { + instructions.push(` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`); + } + + instructions.push(`Then restart your terminal or run: . $PROFILE`); + return instructions; } function getManualSetupInstructions() { - return [ + const customDir = getSafeChainDir(); + const instructions = [ `Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):`, - ` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`, - `Then restart your terminal or run: . $PROFILE`, ]; + + if (customDir) { + instructions.push( + ` $env:SAFE_CHAIN_DIR = '${customDir}'`, + ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`, + ); + } else { + instructions.push(` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`); + } + + instructions.push(`Then restart your terminal or run: . $PROFILE`); + return instructions; } /** diff --git a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js index efb5cc3..840f585 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/windowsPowershell.spec.js @@ -241,6 +241,17 @@ describe("Windows PowerShell shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok(!content.includes("SAFE_CHAIN_DIR")); }); + + it("should show custom manual teardown instructions when custom dir is set", () => { + getSafeChainDirResult = "C:\\custom\\safe-chain"; + + assert.deepStrictEqual(windowsPowershell.getManualTeardownInstructions(), [ + 'Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):', + " $env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain'", + ' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"', + "Then restart your terminal or run: . $PROFILE", + ]); + }); }); describe("execution policy", () => { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/zsh.js b/packages/safe-chain/src/shell-integration/supported-shells/zsh.js index a340424..b2c29e4 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/zsh.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/zsh.js @@ -85,19 +85,37 @@ function getStartupFile() { } function getManualTeardownInstructions() { - return [ - `Remove the following line from your ~/.zshrc file:`, - ` source ~/.safe-chain/scripts/init-posix.sh`, - `Then restart your terminal or run: source ~/.zshrc`, - ]; + const customDir = getSafeChainDir(); + const instructions = [`Remove the following line from your ~/.zshrc file:`]; + + if (customDir) { + instructions.push( + ` export SAFE_CHAIN_DIR="${customDir}"`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + ); + } else { + instructions.push(` source ~/.safe-chain/scripts/init-posix.sh`); + } + + instructions.push(`Then restart your terminal or run: source ~/.zshrc`); + return instructions; } function getManualSetupInstructions() { - return [ - `Add the following line to your ~/.zshrc file:`, - ` source ~/.safe-chain/scripts/init-posix.sh`, - `Then restart your terminal or run: source ~/.zshrc`, - ]; + const customDir = getSafeChainDir(); + const instructions = [`Add the following line to your ~/.zshrc file:`]; + + if (customDir) { + instructions.push( + ` export SAFE_CHAIN_DIR="${customDir}"`, + ` source ${path.join(getScriptsDir(), "init-posix.sh")}`, + ); + } else { + instructions.push(` source ~/.safe-chain/scripts/init-posix.sh`); + } + + instructions.push(`Then restart your terminal or run: source ~/.zshrc`); + return instructions; } export default { diff --git a/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js index 4f1ca88..52e790f 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/zsh.spec.js @@ -206,6 +206,17 @@ describe("Zsh shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok(!content.includes("SAFE_CHAIN_DIR")); }); + + it("should show custom manual teardown instructions when custom dir is set", () => { + getSafeChainDirResult = "/custom/safe-chain"; + + assert.deepStrictEqual(zsh.getManualTeardownInstructions(), [ + "Remove the following line from your ~/.zshrc file:", + ' export SAFE_CHAIN_DIR="/custom/safe-chain"', + " source /test-home/.safe-chain/scripts/init-posix.sh", + "Then restart your terminal or run: source ~/.zshrc", + ]); + }); }); describe("integration tests", () => {