Fix WIndows shell + unit tests

This commit is contained in:
Reinier Criel 2026-04-10 14:27:55 -07:00
parent 1aef941d1c
commit 32c95dbb9d
14 changed files with 289 additions and 43 deletions

View file

@ -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");

View file

@ -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",
);
});
});

View file

@ -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
)
)

View file

@ -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");

View file

@ -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;
}
/**

View file

@ -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", () => {

View file

@ -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;
}
/**

View file

@ -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", () => {

View file

@ -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;
}
/**

View file

@ -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", () => {

View file

@ -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;
}
/**

View file

@ -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", () => {

View file

@ -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 {

View file

@ -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", () => {