@dreamhorizonorg/sentinel
Version:
Open-source, zero-dependency tool that blocks compromised packages BEFORE download. Built to counter supply chain and credential theft attacks like Shai-Hulud.
118 lines (100 loc) • 3.01 kB
JavaScript
/**
* CLI argument parsing utilities
* Pure functions for parsing command-line arguments
*/
import { BOOLEAN_STRING_VALUES } from '../constants/cli.constants.mjs';
/**
* Convert string boolean to actual boolean
*/
const parseBoolean = (value) => {
if (value === BOOLEAN_STRING_VALUES.TRUE) {
return true;
}
if (value === BOOLEAN_STRING_VALUES.FALSE) {
return false;
}
return value;
};
/**
* Parse command-line arguments
* Returns { command, subcommand, options, positional }
*/
export const parseArgs = () => {
const args = process.argv.slice(2);
const command = args[0];
let subcommand = null;
const options = {};
const positional = [];
// Check if command supports subcommands (add, remove)
const hasSubcommand = command === 'add' || command === 'remove';
// If has subcommand, args[1] is the subcommand
if (hasSubcommand && args.length > 1 && !args[1].startsWith('-')) {
subcommand = args[1];
}
// Start parsing from index 1 (or 2 if we have a subcommand)
const startIndex = hasSubcommand && subcommand ? 2 : 1;
for (let i = startIndex; i < args.length; i++) {
const arg = args[i];
// Handle --key=value format
if (arg.startsWith('--') && arg.includes('=')) {
const [key, ...valueParts] = arg.slice(2).split('=');
const value = valueParts.join('='); // Rejoin in case value contains '='
const normalizedKey = key.replace(/-/g, '').toLowerCase();
options[normalizedKey] = parseBoolean(value);
continue;
}
if (arg.startsWith('--')) {
const key = arg.slice(2).replace(/-/g, '').toLowerCase();
const nextArg = args[i + 1];
if (nextArg && !nextArg.startsWith('-')) {
options[key] = parseBoolean(nextArg);
i++;
} else {
options[key] = true;
}
} else if (arg.startsWith('-')) {
const key = arg.slice(1).toLowerCase();
const nextArg = args[i + 1];
if (nextArg && !nextArg.startsWith('-')) {
options[key] = parseBoolean(nextArg);
i++;
} else {
options[key] = true;
}
} else {
positional.push(arg);
}
}
return { command, subcommand, options, positional };
};
/**
* Parse package spec (package-name@version or @scope/package@version)
*/
export const parsePackageSpec = (spec) => {
// Handle scoped packages: @scope/package@version
const scopedMatch = spec.match(/^(@[^/]+\/[^@]+)@?(.+)?$/);
if (scopedMatch) {
return {
name: scopedMatch[1],
version: scopedMatch[2] ?? null
};
}
// Handle regular packages: package@version
const match = spec.match(/^(@?[^@]+)@?(.+)?$/);
if (!match) {
return { name: spec, version: null };
}
return {
name: match[1],
version: match[2] ?? null
};
};
/**
* Check if command is help command
*/
export const isHelpCommand = (command) => {
return !command ||
command === 'help' ||
command === '--help' ||
command === '-h';
};