yargs-file-commands
Version:
A yargs helper function that lets you define your commands structure via directory and file naming conventions.
97 lines • 4.6 kB
JavaScript
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
/**
* Imports a command module from a file
* @async
* @param {string} filePath - Path to the command file
* @param {string} name - Command name
* @param {ImportCommandOptions} options - Import options
* @returns {Promise<CommandModule>} Imported command module
*
* @description
* Dynamically imports a command file and constructs a Yargs command module.
* Supports two styles of command declaration:
* 1. Single export of CommandModule named 'command'
* 2. Individual exports of command parts (command, describe, alias, etc.)
* If no handler is provided, creates a null implementation.
*/
export const importCommandFromFile = async (filePath, name, options) => {
// Resolve to absolute path first
const resolvedPath = path.resolve(filePath);
// Ensure file exists using fs node library
if (!fs.existsSync(resolvedPath)) {
const originalPath = filePath !== resolvedPath ? ` (original: ${filePath})` : '';
throw new Error(`Cannot import command from non-existent file path: ${resolvedPath}${originalPath}. ` +
`Ensure the file exists and the path is correct. If using a relative path, consider using an absolute path or path.resolve().`);
}
// Get the real (canonical) path to handle symlinks consistently
// This ensures the path matches what pathToFileURL will resolve to
const realPath = fs.realpathSync.native(resolvedPath);
// Construct file URL using Node.js's pathToFileURL which properly handles
// path normalization, special characters, and cross-platform compatibility
// Use the real path to avoid symlink resolution mismatches
const url = pathToFileURL(realPath).href;
const { logLevel = 'info' } = options;
// Import the module
let imported;
try {
imported = await import(url);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to import command module from ${realPath}: ${errorMessage}. ` +
`Ensure the file is a valid JavaScript/TypeScript module and the path is correct.`);
}
// Check if this is the default command
const isDefault = name === '$default';
// First try to use the CommandModule export if it exists
const importedRecord = imported;
if ('command' in importedRecord && typeof importedRecord.command === 'object' && importedRecord.command !== null) {
const commandModule = importedRecord.command;
// Ensure the command property exists or use the filename
if (!commandModule.command && !isDefault) {
commandModule.command = name;
}
else if (isDefault && !commandModule.command) {
commandModule.command = '$0';
}
if (logLevel === 'debug') {
console.debug('Importing CommandModule from', realPath, 'as', name, 'with description', commandModule.describe);
}
// Return the command module directly without wrapping
return {
command: commandModule.command,
describe: commandModule.describe,
builder: commandModule.builder,
handler: commandModule.handler,
deprecated: commandModule.deprecated,
aliases: commandModule.aliases,
};
}
// Fall back to individual exports
const handlerModule = importedRecord;
const command = {
command: handlerModule.command ?? (isDefault ? '$0' : name),
describe: handlerModule.describe,
aliases: handlerModule.aliases,
builder: handlerModule.builder,
deprecated: handlerModule.deprecated,
handler: handlerModule.handler ??
(async (_args) => {
// null implementation
}),
};
// Validate exports
const supportedNames = ['command', 'describe', 'alias', 'builder', 'deprecated', 'handler'];
const module = importedRecord;
const unsupportedExports = Object.keys(module).filter((key) => !supportedNames.includes(key));
if (unsupportedExports.length > 0) {
throw new Error(`Command module ${name} in ${realPath} has some unsupported exports, probably a misspelling: ${unsupportedExports.join(', ')}. Supported exports are: ${supportedNames.join(', ')}.`);
}
if (logLevel === 'debug') {
console.debug('Importing individual exports from', realPath, 'as', name, 'with description', command.describe);
}
return command;
};
//# sourceMappingURL=importCommand.js.map