mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 20:20:49 +00:00
Adapt the structure to parse the initial pip commands
This commit is contained in:
parent
982da4aa77
commit
1b82aeb6b0
7 changed files with 273 additions and 22 deletions
|
|
@ -1,6 +1,11 @@
|
|||
import { ui } from "../../environment/userInteraction.js";
|
||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||
import { commandArgumentScanner } from "./dependencyScanner/commandArgumentScanner.js";
|
||||
import { nullScanner } from "./dependencyScanner/nullScanner.js";
|
||||
import { runPip } from "./runPipCommand.js";
|
||||
import {
|
||||
getPipCommandForArgs,
|
||||
pipInstallCommand,
|
||||
pipUninstallCommand,
|
||||
} from "./utils/pipCommands.js";
|
||||
|
||||
/**
|
||||
* Creates a package manager interface for Python's pip package installer
|
||||
|
|
@ -8,28 +13,40 @@ import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/reg
|
|||
* @param {string} [command="pip"] - The pip command to use (e.g., "pip", "pip3") defaults to "pip"
|
||||
*/
|
||||
export function createPipPackageManager(command = "pip") {
|
||||
return {
|
||||
runCommand: (args) => runPipCommand(command, args),
|
||||
function isSupportedCommand(args) {
|
||||
const scanner = findDependencyScannerForCommand(
|
||||
commandScannerMapping,
|
||||
args
|
||||
);
|
||||
return scanner.shouldScan(args);
|
||||
}
|
||||
|
||||
// For pip, set proxy server
|
||||
isSupportedCommand: () => false,
|
||||
getDependencyUpdatesForCommand: () => [],
|
||||
function getDependencyUpdatesForCommand(args) {
|
||||
const scanner = findDependencyScannerForCommand(
|
||||
commandScannerMapping,
|
||||
args
|
||||
);
|
||||
return scanner.scan(args);
|
||||
}
|
||||
|
||||
return {
|
||||
runCommand: (args) => runPip(command, args),
|
||||
isSupportedCommand,
|
||||
getDependencyUpdatesForCommand,
|
||||
};
|
||||
}
|
||||
|
||||
async function runPipCommand(command, args) {
|
||||
try {
|
||||
const result = await safeSpawn(command, args, {
|
||||
stdio: "inherit",
|
||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||
});
|
||||
return { status: result.status };
|
||||
} catch (error) {
|
||||
if (error.status) {
|
||||
return { status: error.status };
|
||||
} else {
|
||||
ui.writeError("Error executing command:", error.message);
|
||||
return { status: 1 };
|
||||
}
|
||||
const commandScannerMapping = {
|
||||
[pipInstallCommand]: commandArgumentScanner(),
|
||||
[pipUninstallCommand]: nullScanner(), // Uninstall doesn't need scanning
|
||||
};
|
||||
|
||||
function findDependencyScannerForCommand(scanners, args) {
|
||||
const command = getPipCommandForArgs(args);
|
||||
if (!command) {
|
||||
return nullScanner();
|
||||
}
|
||||
|
||||
const scanner = scanners[command];
|
||||
return scanner ? scanner : nullScanner();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Scanner for pip command arguments to detect package installations
|
||||
*
|
||||
* @param {Object} options - Scanner options
|
||||
* @param {boolean} [options.ignoreDryRun=false] - Whether to ignore dry-run flag
|
||||
* @returns {Object} Scanner interface
|
||||
*/
|
||||
export function commandArgumentScanner(options = {}) {
|
||||
const { ignoreDryRun = false } = options;
|
||||
|
||||
function shouldScan(args) {
|
||||
// For now, pip scanning is not yet implemented
|
||||
// This would need to detect 'install' commands and package arguments
|
||||
return false;
|
||||
}
|
||||
|
||||
function scan(args) {
|
||||
// Future implementation would parse pip install arguments
|
||||
// and return array of {name, version, type} objects
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
shouldScan,
|
||||
scan,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Null scanner that returns no dependencies
|
||||
* Used when a command is not supported for scanning
|
||||
*/
|
||||
export function nullScanner() {
|
||||
return {
|
||||
shouldScan: () => false,
|
||||
scan: () => [],
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Parses package specifications from pip install arguments
|
||||
*
|
||||
* Pip supports various package specification formats:
|
||||
* - package_name
|
||||
* - package_name==version
|
||||
* - package_name>=version
|
||||
* - package_name~=version
|
||||
* - git+https://...
|
||||
* - -r requirements.txt
|
||||
* - . (local directory)
|
||||
*
|
||||
* @param {string[]} args - pip install command arguments
|
||||
* @returns {Array<{name: string, version?: string, type: string}>} Array of package specifications
|
||||
*/
|
||||
export function parsePackagesFromInstallArgs(args) {
|
||||
const packages = [];
|
||||
let skipNext = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
|
||||
if (skipNext) {
|
||||
skipNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the command itself (install, uninstall, etc.)
|
||||
if (i === 0 && !arg.startsWith("-")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip flags and their values
|
||||
if (arg.startsWith("-")) {
|
||||
// Some flags take a value, skip the next arg for those
|
||||
if (arg === "-r" || arg === "--requirement" ||
|
||||
arg === "-c" || arg === "--constraint" ||
|
||||
arg === "-e" || arg === "--editable" ||
|
||||
arg === "-t" || arg === "--target" ||
|
||||
arg === "-i" || arg === "--index-url" ||
|
||||
arg === "--extra-index-url") {
|
||||
skipNext = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Implement full parsing logic
|
||||
// For now, this is a placeholder that would need to handle:
|
||||
// - Version specifiers (==, >=, <=, ~=, !=, <, >)
|
||||
// - VCS urls (git+, hg+, svn+, bzr+)
|
||||
// - Local file paths
|
||||
// - Requirements files (-r, --requirement)
|
||||
// - Extras (package[extra1,extra2])
|
||||
|
||||
packages.push({
|
||||
name: arg,
|
||||
type: "add",
|
||||
});
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { parsePackagesFromInstallArgs } from "./parsePackagesFromInstallArgs.js";
|
||||
|
||||
describe("parsePackagesFromInstallArgs", () => {
|
||||
it("should parse simple package name", () => {
|
||||
const result = parsePackagesFromInstallArgs(["install", "requests"]);
|
||||
assert.deepEqual(result, [
|
||||
{ name: "requests", type: "add" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse package with version specifier", () => {
|
||||
const result = parsePackagesFromInstallArgs(["install", "requests==2.28.0"]);
|
||||
assert.deepEqual(result, [
|
||||
{ name: "requests==2.28.0", type: "add" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should skip flags", () => {
|
||||
const result = parsePackagesFromInstallArgs(["install", "--upgrade", "requests"]);
|
||||
assert.deepEqual(result, [
|
||||
{ name: "requests", type: "add" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should parse multiple packages", () => {
|
||||
const result = parsePackagesFromInstallArgs(["install", "requests", "flask", "django==4.0"]);
|
||||
assert.deepEqual(result, [
|
||||
{ name: "requests", type: "add" },
|
||||
{ name: "flask", type: "add" },
|
||||
{ name: "django==4.0", type: "add" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return empty array for no packages", () => {
|
||||
const result = parsePackagesFromInstallArgs(["install", "--help"]);
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
});
|
||||
66
packages/safe-chain/src/packagemanager/pip/runPipCommand.js
Normal file
66
packages/safe-chain/src/packagemanager/pip/runPipCommand.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { ui } from "../../environment/userInteraction.js";
|
||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||
|
||||
/**
|
||||
* Runs a pip command with the specified arguments
|
||||
*
|
||||
* @param {string} command - The pip command to use (e.g., "pip", "pip3")
|
||||
* @param {string[]} args - Command arguments
|
||||
* @returns {Promise<{status: number}>} Result object with status code
|
||||
*/
|
||||
export async function runPip(command, args) {
|
||||
try {
|
||||
const result = await safeSpawn(command, args, {
|
||||
stdio: "inherit",
|
||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||
});
|
||||
return { status: result.status };
|
||||
} catch (error) {
|
||||
if (error.status) {
|
||||
return { status: error.status };
|
||||
} else {
|
||||
ui.writeError("Error executing command:", error.message);
|
||||
return { status: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a pip command in dry-run mode and captures output
|
||||
* Note: pip doesn't have a native --dry-run flag, so this may need adjustment
|
||||
*
|
||||
* @param {string} command - The pip command to use
|
||||
* @param {string[]} args - Command arguments
|
||||
* @returns {Promise<{status: number, output: string}>} Result with status and output
|
||||
*/
|
||||
export async function dryRunPipCommandAndOutput(command, args) {
|
||||
try {
|
||||
// Note: pip doesn't have a --dry-run flag like npm
|
||||
// This would need to be implemented differently if dry-run functionality is needed
|
||||
const result = await safeSpawn(
|
||||
command,
|
||||
args,
|
||||
{
|
||||
stdio: "pipe",
|
||||
env: mergeSafeChainProxyEnvironmentVariables(process.env),
|
||||
}
|
||||
);
|
||||
return {
|
||||
status: result.status,
|
||||
output: result.status === 0 ? result.stdout : result.stderr,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.status) {
|
||||
const output =
|
||||
error.stdout?.toString() ??
|
||||
error.stderr?.toString() ??
|
||||
error.message ??
|
||||
"";
|
||||
return { status: error.status, output };
|
||||
} else {
|
||||
ui.writeError("Error executing command:", error.message);
|
||||
return { status: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Pip command constants
|
||||
*/
|
||||
export const pipInstallCommand = "install";
|
||||
export const pipUninstallCommand = "uninstall";
|
||||
export const pipListCommand = "list";
|
||||
export const pipShowCommand = "show";
|
||||
export const pipFreeze = "freeze";
|
||||
|
||||
/**
|
||||
* Gets the pip command from the arguments array
|
||||
*
|
||||
* @param {string[]} args - Command line arguments
|
||||
* @returns {string|null} The pip command or null if not found
|
||||
*/
|
||||
export function getPipCommandForArgs(args) {
|
||||
if (!args || args.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The first non-flag argument is typically the command
|
||||
for (const arg of args) {
|
||||
if (!arg.startsWith("-")) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue