This commit is contained in:
Reinier Criel 2025-12-15 14:42:58 +01:00
parent fc5df6cd14
commit dc6fcb9761
6 changed files with 697 additions and 0 deletions

View file

@ -0,0 +1,16 @@
#!/usr/bin/env node
import { main } from "../src/main.js";
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
// Set eco system
setEcoSystem(ECOSYSTEM_PY);
initializePackageManager("pipx");
(async () => {
// Pass through only user-supplied pipx args
var exitCode = await main(process.argv.slice(2));
process.exit(exitCode);
})();

View file

@ -0,0 +1,18 @@
import { runPipX } from "./runPipXCommand.js";
/**
* @returns {import("../currentPackageManager.js").PackageManager}
*/
export function createPipXPackageManager() {
return {
/**
* @param {string[]} args
*/
runCommand: (args) => {
return runPipX("pipx", args);
},
// For uv, rely solely on MITM
isSupportedCommand: () => false,
getDependencyUpdatesForCommand: () => [],
};
}

View file

@ -0,0 +1,14 @@
import { test } from "node:test";
import assert from "node:assert";
import { createPipXPackageManager } from "./createPipXPackageManager.js";
test("createPipXPackageManager", async (t) => {
await t.test("should create package manager with required interface", () => {
const pm = createPipXPackageManager();
assert.ok(pm);
assert.strictEqual(typeof pm.runCommand, "function");
assert.strictEqual(typeof pm.isSupportedCommand, "function");
assert.strictEqual(typeof pm.getDependencyUpdatesForCommand, "function");
});
});

View file

@ -0,0 +1,71 @@
import { ui } from "../../environment/userInteraction.js";
import { safeSpawn } from "../../utils/safeSpawn.js";
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
/**
* Sets CA bundle environment variables used by Python libraries and pipx.
*
* @param {NodeJS.ProcessEnv} env - Env object
* @param {string} combinedCaPath - Path to the combined CA bundle
*/
function setPipXCaBundleEnvironmentVariables(env, combinedCaPath) {
// SSL_CERT_FILE: Used by Python SSL libraries and underlying HTTP clients
if (env.SSL_CERT_FILE) {
ui.writeWarning("Safe-chain: User defined SSL_CERT_FILE found in environment. It will be overwritten.");
}
env.SSL_CERT_FILE = combinedCaPath;
// REQUESTS_CA_BUNDLE: Used by the requests library (which uv may use internally)
if (env.REQUESTS_CA_BUNDLE) {
ui.writeWarning("Safe-chain: User defined REQUESTS_CA_BUNDLE found in environment. It will be overwritten.");
}
env.REQUESTS_CA_BUNDLE = combinedCaPath;
// PIP_CERT: Some underlying pip operations may respect this
if (env.PIP_CERT) {
ui.writeWarning("Safe-chain: User defined PIP_CERT found in environment. It will be overwritten.");
}
env.PIP_CERT = combinedCaPath;
}
/**
* Runs a uv command with safe-chain's certificate bundle and proxy configuration.
*
* uv respects standard environment variables for proxy and TLS configuration:
* - HTTP_PROXY / HTTPS_PROXY: Proxy settings
* - SSL_CERT_FILE / REQUESTS_CA_BUNDLE: CA bundle for TLS verification
*
* Unlike pip (which requires a temporary config file for cert configuration), uv directly
* honors environment variables, so no config/ini file is needed.
*
* @param {string} command - The pipx command to execute
* @param {string[]} args - Command line arguments to pass to pipx
* @returns {Promise<{status: number}>} Exit status of the pipx command
*/
export async function runPipX(command, args) {
try {
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
const combinedCaPath = getCombinedCaBundlePath();
setPipXCaBundleEnvironmentVariables(env, combinedCaPath);
// Note: pipx uses HTTPS_PROXY and HTTP_PROXY environment variables for proxy configuration
// These are already set by mergeSafeChainProxyEnvironmentVariables
const result = await safeSpawn(command, args, {
stdio: "inherit",
env,
});
return { status: result.status };
} catch (/** @type any */ error) {
if (error.status) {
return { status: error.status };
} else {
ui.writeError(`Error executing command: ${error.message}`);
ui.writeError(`Is '${command}' installed and available on your system?`);
return { status: 1 };
}
}
}

View file

@ -94,6 +94,12 @@ export const knownAikidoTools = [
ecoSystem: ECOSYSTEM_PY,
internalPackageManagerName: "pip",
},
{
tool: "pipx",
aikidoCommand: "aikido-pipx",
ecoSystem: ECOSYSTEM_PY,
internalPackageManagerName: "pip",
}
// When adding a new tool here, also update the documentation for the new tool in the README.md
];