UNPKG

yargs-file-commands

Version:

A yargs helper function that lets you define your commands structure via directory and file naming conventions.

104 lines 4.88 kB
import yargs from 'yargs'; /** * Extracts positional argument names from a command string. * Handles patterns like: "create <name>", "create <name> [optional]", etc. * * @param commandString - The command string (e.g., "create <serviceAccount> <token>") * @returns Array of positional argument names found in the command string */ export function extractPositionalsFromCommandString(commandString) { if (!commandString) { return []; } // Handle array of command strings (take the first one) const cmd = Array.isArray(commandString) ? commandString[0] : commandString; if (!cmd || typeof cmd !== 'string') { return []; } // Match patterns like <name> or [name] in the command string // Examples: // - "create <serviceAccount> <token>" -> ["serviceAccount", "token"] // - "create <name> [optional]" -> ["name", "optional"] // - "create" -> [] const positionalPattern = /[<[]([^>\]]+)[>\]]/g; const positionals = []; let match = positionalPattern.exec(cmd); while (match !== null) { if (match[1]) { positionals.push(match[1]); } match = positionalPattern.exec(cmd); } return positionals; } /** * Extracts positional argument names from a builder function by executing it * with a mock yargs instance and tracking what positionals are registered. * * @param builder - The command builder function * @returns Array of positional argument names registered in the builder */ export async function extractPositionalsFromBuilder(builder) { if (!builder) { return []; } // CommandBuilder can be a function or an object - we only validate function builders if (typeof builder !== 'function') { // Object builders don't use .positional() calls, so no positionals to extract return []; } // Track positional arguments by creating a wrapper around yargs const positionals = []; const mockYargs = yargs([]); // Wrap the positional method to track calls const originalPositional = mockYargs.positional.bind(mockYargs); // biome-ignore lint/suspicious/noExplicitAny: Need to override yargs positional method to track calls mockYargs.positional = (name, config) => { positionals.push(name); return originalPositional(name, config); }; // Execute the builder function try { const result = builder(mockYargs); // Handle both sync and async builders await (result instanceof Promise ? result : Promise.resolve(result)); } catch { // If builder throws, we can't validate - this is okay, yargs will catch it later // We'll just return what we've collected so far } return positionals; } /** * Validates that positional arguments registered in the builder match * those declared in the command string. * * @param commandModule - The command module to validate * @param filePath - Path to the command file (for error messages) * @throws Error if validation fails and positionals don't match */ export async function validatePositionals(commandModule, filePath) { const commandString = commandModule.command; const builder = commandModule.builder; // Extract positionals from command string const commandStringPositionals = extractPositionalsFromCommandString(commandString); // Extract positionals from builder const builderPositionals = await extractPositionalsFromBuilder(builder); // If builder has positionals but command string doesn't, that's an error if (builderPositionals.length > 0 && commandStringPositionals.length === 0) { throw new Error(`Command in ${filePath} has ${builderPositionals.length} positional argument(s) registered in builder ` + `(${builderPositionals.join(', ')}) but none declared in command string "${commandString}". ` + `Positional arguments must be declared in the command string (e.g., "create <arg1> <arg2>").`); } // Check if all builder positionals are in command string const missingInCommandString = builderPositionals.filter((pos) => !commandStringPositionals.includes(pos)); if (missingInCommandString.length > 0) { throw new Error(`Command in ${filePath} has positional argument(s) registered in builder ` + `(${missingInCommandString.join(', ')}) that are not declared in command string "${commandString}". ` + `All positional arguments must be declared in the command string (e.g., "create <arg1> <arg2>").`); } // Check if command string has positionals not in builder (warning case, but not an error) // This is allowed - you can declare positionals in command string without registering them // But we'll still validate the reverse (builder positionals must be in command string) } //# sourceMappingURL=validatePositionals.js.map