UNPKG

@black-flag/core

Version:

A declarative framework for building fluent, deeply hierarchical command line interfaces with yargs

665 lines (664 loc) 36.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.discoverCommands = discoverCommands; require("core-js/modules/es.array.push.js"); require("core-js/modules/es.iterator.constructor.js"); require("core-js/modules/es.iterator.filter.js"); require("core-js/modules/es.iterator.for-each.js"); require("core-js/modules/es.iterator.map.js"); require("core-js/modules/esnext.json.parse.js"); var _nodeAssert = _interopRequireDefault(require("node:assert")); var _promises = _interopRequireDefault(require("node:fs/promises")); var _nodePath = require("node:path"); var _nodeUrl = require("node:url"); var _types = require("node:util/types"); var _fs = require("@-xun/fs"); var _constant = require("./constant.js"); var _error = require("./error.js"); var _util = require("./util.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const hasSpacesRegExp = /\s+/; const isValidYargsCommandDslRegExp = /^\$0( ((<[^>]*>)|(\[[^\]]*\])))*$/; const alphaSort = new Intl.Collator(undefined, { numeric: true }); async function discoverCommands(basePath_, context) { let alreadyLoadedRootCommand = false; if (!basePath_ || typeof basePath_ !== 'string') { throw new _error.AssertionFailedError(_error.BfErrorMessage.BadConfigurationPath(basePath_)); } const basePath = (0, _fs.toAbsolutePath)(basePath_.startsWith('file://') ? (0, _nodeUrl.fileURLToPath)(basePath_) : basePath_); const discoverDebug = context.debug.extend('discover'); const discoveredDebug = discoverDebug.extend('discovered'); discoverDebug('ensuring base path directory exists and is readable: "%O"', basePath); try { await _promises.default.access(basePath, _promises.default.constants.R_OK); if (!(await _promises.default.stat(basePath)).isDirectory()) { throw new Error(_error.BfErrorMessage.PathIsNotDirectory()); } } catch (error) { discoverDebug.error('failed due to invalid base path "%O": %O', basePath, error); throw new _error.AssertionFailedError(_error.BfErrorMessage.BadConfigurationPath(basePath)); } discoverDebug('searching upwards for nearest package.json file starting at %O', basePath); const package_ = { path: await (await import('package-up')).packageUp({ cwd: basePath }), name: undefined, version: undefined }; if (package_.path) { discoverDebug('loading package.json file from %O', package_.path); try { const { name, version } = JSON.parse(await _promises.default.readFile(package_.path, 'utf8')); package_.name = name; package_.version = version; } catch (error) { discoverDebug.warn('load failed: attempt to import %O failed: %O', package_.path, error); } } else { discoverDebug.warn('search failed: no package.json file found'); } discoverDebug('relevant cli package.json data discovered: %O', package_); if (context.state.globalVersionOption) { if (!context.state.globalVersionOption.text) { context.state.globalVersionOption.text = package_.version || ''; } if (!context.state.globalVersionOption.text) { context.state.globalVersionOption = undefined; discoverDebug.warn('disabled built-in version option (globalVersionOption=undefined) since no version info available'); } } discoverDebug('beginning configuration module auto-discovery at %O', basePath); await discover(basePath); discoverDebug('configuration module auto-discovery completed'); if (context.commands.size) { discoveredDebug.message('%O commands loaded: %O', context.commands.size, context.commands.keys()); discoveredDebug.message('%O', context.commands); } else { throw new _error.AssertionFailedError(_error.BfErrorMessage.NoConfigurationLoaded(basePath)); } return; async function discover(configPath, lineage = [], grandparentPrograms = undefined, grandparentMeta = undefined) { const isRootCommand = !alreadyLoadedRootCommand; const parentType = isRootCommand ? 'pure parent' : 'parent-child'; const depth = lineage.length; discoverDebug('initial parent lineage: %O', lineage); discoverDebug('is root (aka "pure parent") command: %O', isRootCommand); (0, _nodeAssert.default)(grandparentPrograms === undefined || !isRootCommand, _error.BfErrorMessage.GuruMeditation()); const { configuration: parentConfig, metadata: parentMeta } = await loadConfiguration(['js', 'mjs', 'cjs', 'ts', 'mts', 'cts'].map(extension => (0, _fs.toPath)(configPath, `index.${extension}`))); if (!parentConfig) { discoverDebug.warn(`skipped ${parentType} configuration (depth: %O) due to missing or unloadable index file in directory %O`, depth, configPath); return; } ensureConfigurationDoesNotConflictWithReservedNames(parentConfig, lineage.join(' ')); lineage = [...lineage, parentConfig.name]; const parentConfigFullName = lineage.join(' '); discoverDebug('updated parent lineage: %O', lineage); discoverDebug('command full name: %O', parentConfigFullName); const parentPrograms = await makeCommandPrograms(parentConfig, parentMeta, parentConfigFullName, parentType); context.commands.set(parentConfigFullName, { programs: parentPrograms, metadata: parentMeta }); discoverDebug(`added ${parentType} command mapping to ExecutionContext::commands`); linkChildRouterToParentRouter(parentPrograms.router, parentConfig, parentConfigFullName, grandparentPrograms?.router); addChildCommandToParent(parentConfig, parentConfigFullName, grandparentPrograms); if (grandparentMeta) { grandparentMeta.hasChildren = true; } discoveredDebug(`discovered ${parentType} configuration (depth: %O) for command %O`, depth, parentConfigFullName); const configDir = await _promises.default.opendir(configPath); for await (const entry of configDir) { const isPotentialChildConfigOfCurrentParent = /.*(?<!index)\.(?:js|mjs|cjs|ts|mts|cts)$/.test(entry.name) && !entry.name.endsWith('.d.ts'); const entryFullPath = (0, _fs.toPath)((0, _fs.toAbsolutePath)(entry.parentPath), entry.name); discoverDebug('saw potential child configuration file: %O', entryFullPath); if (entry.isDirectory()) { discoverDebug('file is actually a directory, recursing'); await discover(entryFullPath, lineage, parentPrograms, parentMeta); } else if (isPotentialChildConfigOfCurrentParent) { discoverDebug('attempting to load file'); const { configuration: childConfig, metadata: childMeta } = await loadConfiguration(entryFullPath); if (!childConfig) { discoverDebug.error(`failed to load pure child configuration (depth: %O) due to missing or invalid file %O`, depth, entryFullPath); throw new _error.AssertionFailedError(_error.BfErrorMessage.ConfigLoadFailure(entryFullPath)); } const childConfigFullName = `${parentConfigFullName} ${childConfig.name}`; discoverDebug('pure child full name (lineage): %O', childConfigFullName); ensureConfigurationDoesNotConflictWithReservedNames(childConfig, parentConfigFullName); const childPrograms = await makeCommandPrograms(childConfig, childMeta, childConfigFullName, 'pure child'); context.commands.set(childConfigFullName, { programs: childPrograms, metadata: childMeta }); discoverDebug(`added pure child command mapping to ExecutionContext::commands`); linkChildRouterToParentRouter(childPrograms.router, childConfig, childConfigFullName, parentPrograms.router); addChildCommandToParent(childConfig, childConfigFullName, parentPrograms); parentMeta.hasChildren = true; discoveredDebug(`discovered pure child configuration (depth: %O) for command %O`, depth + 1, childConfigFullName); } else { discoverDebug('file was ignored (only non-index sibling JS files are considered at this stage)'); } } } async function loadConfiguration(configPath) { const isRootProgram = !alreadyLoadedRootCommand; const loadDebug = discoverDebug.extend('load-configuration'); const maybeConfigPaths = [configPath].flat().map(p => p.trim()).filter(Boolean); loadDebug('loading new configuration from the first readable path: %O', maybeConfigPaths); while (maybeConfigPaths.length) { try { const maybeConfigPath = maybeConfigPaths.shift(); const maybeConfigFileURL = (0, _nodeUrl.pathToFileURL)(maybeConfigPath).toString(); loadDebug('attempting to load configuration file url: %O (real path: %O)', maybeConfigFileURL, maybeConfigPath); const maybeImportedConfig = await (async () => { try { return await import(maybeConfigFileURL); } catch (error) { if (isUnknownFileExtensionSystemError(error) || isModuleNotFoundSystemError(error) && (error.moduleName?.endsWith(maybeConfigPath) || error.url === maybeConfigFileURL.toString())) { loadDebug.warn('a recoverable failure occurred while attempting to load configuration: %O', error); } else { throw error; } } })(); loadDebug('maybeImportedConfig: %O', maybeImportedConfig); if (maybeImportedConfig) { const meta = { filename: (0, _nodePath.basename)(maybeConfigPath), filepath: maybeConfigFileURL, hasChildren: false, parentDirName: (0, _nodePath.basename)((0, _fs.toDirname)(maybeConfigPath)), get filenameWithoutExtension() { return _nodeAssert.default.fail(_error.BfErrorMessage.GuruMeditation()); }, set filenameWithoutExtension(v) { delete meta.filenameWithoutExtension; meta.filenameWithoutExtension = v; }, get fullUsageText() { return _nodeAssert.default.fail(_error.BfErrorMessage.GuruMeditation()); }, set fullUsageText(v) { delete meta.fullUsageText; meta.fullUsageText = v; }, get isImplemented() { return _nodeAssert.default.fail(_error.BfErrorMessage.GuruMeditation()); }, set isImplemented(v) { delete meta.isImplemented; meta.isImplemented = v; }, get reservedCommandNames() { return _nodeAssert.default.fail(_error.BfErrorMessage.GuruMeditation()); }, set reservedCommandNames(v) { delete meta.reservedCommandNames; meta.reservedCommandNames = v; }, get type() { return _nodeAssert.default.fail(_error.BfErrorMessage.GuruMeditation()); }, set type(v) { delete meta.type; meta.type = v; } }; meta.filenameWithoutExtension = meta.filename.split('.').slice(0, -1).join('.'); const isParentProgram = meta.filenameWithoutExtension === 'index'; meta.type = isRootProgram ? 'pure parent' : isParentProgram ? 'parent-child' : 'pure child'; loadDebug('configuration file metadata (w/o reservedCommandNames): %O', meta); let importedConfig = maybeImportedConfig; if (importedConfig.default) { importedConfig = importedConfig.default; } if (importedConfig.default) { importedConfig = importedConfig.default; } let rawConfig; if (typeof importedConfig === 'function') { loadDebug('configuration returned a function'); rawConfig = await importedConfig(context); } else { loadDebug('configuration returned an object (or something coerced into {})'); rawConfig = typeof importedConfig === 'object' ? importedConfig : {}; } rawConfig = { ...rawConfig }; const command = (rawConfig.command ?? '$0').trim(); meta.isImplemented = rawConfig.handler !== undefined; const handlerDebug = loadDebug.extend('handler'); const finalConfig = { aliases: rawConfig.aliases?.map(str => replaceSpaces(str).trim()) || [], builder: rawConfig.builder || {}, command, deprecated: rawConfig.deprecated ?? false, description: rawConfig.description ?? '', handler(...args) { handlerDebug('executing real handler function for %O', finalConfig.name); return (rawConfig.handler || defaultCommandHandler)(...args); }, name: replaceSpaces(rawConfig.name || (isRootProgram && package_.name ? package_.name : isParentProgram ? meta.parentDirName : meta.filenameWithoutExtension)).trim(), usage: (0, _util.capitalize)(rawConfig.usage || _constant.defaultUsageText).trim() }; finalConfig.description = typeof finalConfig.description === 'string' ? (0, _util.capitalize)(finalConfig.description).trim() : finalConfig.description; loadDebug.message('successfully loaded configuration object: %O', finalConfig); loadDebug('validating loaded configuration object for correctness'); for (const name of [finalConfig.name, ...finalConfig.aliases]) { if (hasSpacesRegExp.test(name)) { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvalidCharacters(name, 'space(s)')); } if (name.includes('$0')) { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvalidCharacters(name, '$0')); } if (name.includes('$1')) { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvalidCharacters(name, '$1')); } if (name.includes('|') || name.includes('<') || name.includes('>') || name.includes('[') || name.includes(']') || name.includes('{') || name.includes('}')) { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvalidCharacters(name, '|, <, >, [, ], {, or }')); } } if (finalConfig.command !== '$0' && (!finalConfig.command.startsWith('$0') || !finalConfig.command.startsWith('$0 '))) { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvalidCommandExportBadStart(finalConfig.name)); } if (!isValidYargsCommandDslRegExp.test(finalConfig.command)) { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvalidCommandExportBadPositionals(finalConfig.name)); } loadDebug('configuration is valid!'); alreadyLoadedRootCommand = true; meta.reservedCommandNames = [finalConfig.name, ...finalConfig.aliases]; loadDebug('metadata reserved command names: %O', meta.reservedCommandNames); return { configuration: finalConfig, metadata: meta }; } } catch (error) { loadDebug.error('an irrecoverable failure occurred while loading configuration: %O', error); throw error; } } return { configuration: undefined, metadata: undefined }; } function ensureConfigurationDoesNotConflictWithReservedNames(config, parentFullName) { let checkCount = 0; [config.name, ...config.aliases].forEach((reservedName, index, reservedNames) => { [...reservedNames.slice(0, index), ...reservedNames.slice(index + 1)].forEach((reservedName_, index_) => { checkCount++; if (reservedName === reservedName_) { throw new _error.AssertionFailedError(_error.BfErrorMessage.DuplicateCommandName(parentFullName, reservedName, index === 0 ? 'name' : 'alias', reservedName_, index !== 0 && index_ === 0 ? 'name' : 'alias')); } }); }); (0, _nodeAssert.default)(typeof parentFullName === 'string', _error.BfErrorMessage.GuruMeditation()); context.commands.forEach((command, commandName) => { const splitCommandName = commandName.split(' '); const seenParentFullName = splitCommandName.slice(0, -1).join(' '); const seenName = splitCommandName.at(-1); if (seenParentFullName === parentFullName) { command.metadata.reservedCommandNames.forEach((reservedName, index) => { (0, _nodeAssert.default)(index !== 0 || seenName === reservedName, _error.BfErrorMessage.GuruMeditation()); checkCount++; if (reservedName === config.name) { throw new _error.AssertionFailedError(_error.BfErrorMessage.DuplicateCommandName(parentFullName, config.name, 'name', reservedName, index === 0 ? 'name' : 'alias')); } config.aliases.forEach(aliasName => { checkCount++; if (reservedName === aliasName) { throw new _error.AssertionFailedError(_error.BfErrorMessage.DuplicateCommandName(parentFullName, aliasName, 'alias', reservedName, index === 0 ? 'name' : 'alias')); } }); }); } }); discoverDebug('no reserved name conflicts detected (%O checks performed)', checkCount); } async function makeCommandPrograms(config, meta, fullName, type) { const makerDebug = discoverDebug.extend('make:programs'); if (type === 'pure parent' && config.aliases.length) { makerDebug.warn('root command aliases will be ignored during routing and will not appear in help text: %O', config.aliases); } const programs = { effector: await makePartiallyConfiguredProgram('effector'), helper: await makePartiallyConfiguredProgram('helper'), router: await makePartiallyConfiguredProgram('router') }; if (type === 'pure parent' && context.state.globalVersionOption?.name.length) { const { name: versionOptionName, description: versionOptionDescription } = context.state.globalVersionOption; (0, _nodeAssert.default)(versionOptionName.length, _error.BfErrorMessage.GuruMeditation()); [programs.helper, programs.effector].forEach(program => program.option(versionOptionName, { boolean: true, description: versionOptionDescription })); } programs.effector.strict(true); meta.fullUsageText = config.usage.replaceAll('$000', config.command).replaceAll('$1', config.description || '').trim(); programs.helper.scriptName(fullName); programs.effector.scriptName(fullName); programs.helper.wrap(context.state.initialTerminalWidth); programs.effector.wrap(context.state.initialTerminalWidth); programs.helper.fail(customFailHandler('helper')); programs.effector.fail(customFailHandler('effector')); const routerDebug = discoverDebug.extend('router@'); programs.router.command(['$0'], '[routed-1]', {}, async function () { routerDebug('control reserved; calling HelperProgram::parseAsync'); await programs.helper.parseAsync(context.state.rawArgv, (0, _util.wrapExecutionContext)(context)); }, [], false); programs.helper.command_deferred([makeRequiredPositionalsOptional(config.command), ...config.aliases], false, makeVanillaYargsBuilder(programs.helper, config, 'first-pass'), async parsedArgv => { const helperDebug = discoverDebug.extend('helper'); helperDebug('entered wrapper handler function for %O', config.name); (0, _nodeAssert.default)(context.state.firstPassArgv === undefined, _error.BfErrorMessage.GuruMeditation()); context.state.firstPassArgv = parsedArgv; if (context.state.isHandlingHelpOption) { helperDebug('sending help text to stdout (triggered by the %O option)', context.state.globalHelpOption?.name || '???'); setContextualUsageTextFor(meta.fullUsageText, 'full'); programs.effector.showHelp('log'); context.state.didOutputHelpOrVersionText = true; helperDebug('gracefully exited wrapper handler function for %O', config.name); throw new _error.GracefulEarlyExitError(); } else if (type === 'pure parent' && context.state.isHandlingVersionOption) { helperDebug('sending version text to stdout (triggered by the %O option)', context.state.globalVersionOption?.name || '???'); console.log(context.state.globalVersionOption?.text || '???'); context.state.didOutputHelpOrVersionText = true; helperDebug('gracefully exited wrapper handler function for %O', config.name); throw new _error.GracefulEarlyExitError(); } else { if (!meta.isImplemented && meta.hasChildren) { helperDebug.message('short-circuited effector: command is both unimplemented and has children'); throw new _error.CommandNotImplementedError(); } const localArgv = await programs.effector.parseAsync(context.state.rawArgv, (0, _util.wrapExecutionContext)(context)); (0, _nodeAssert.default)(context.state.deepestParseResult === undefined, _error.BfErrorMessage.GuruMeditation()); context.state.deepestParseResult = localArgv; helperDebug('context.state.deepestParseResult set to EffectorProgram::parseAsync result: %O', localArgv); helperDebug('exited wrapper handler function for %O', config.name); } }, [], config.deprecated); programs.effector.command_deferred([config.command, ...config.aliases], false, makeVanillaYargsBuilder(programs.effector, config, 'second-pass'), config.handler, [], config.deprecated); makerDebug('created and fully configured the effector, helper, and router programs for %O command %O', type, fullName); return programs; function customFailHandler(descriptor) { return function (message, error) { const descriptorDebug = discoverDebug.extend(`${descriptor}@`); descriptorDebug.message('entered failure handler for command %O', fullName); const { showHelpOnFail } = context.state; descriptorDebug('message: %O', message); descriptorDebug('error.message: %O', error?.message); descriptorDebug('error is native error: %O', (0, _types.isNativeError)(error)); const isErrorCliError = (0, _error.isCliError)(error); descriptorDebug('error is CliError: %O', isErrorCliError); const isErrorVanillaYargsError = (0, _error.isYargsError)(error) || !error; descriptorDebug('error is vanilla yargs error: %O', isErrorVanillaYargsError); const isErrorOtherError = !isErrorCliError && !isErrorVanillaYargsError; descriptorDebug('error is other error: %O', isErrorOtherError); descriptorDebug('is allowed to show help on fail: %O (%O)', !!showHelpOnFail, showHelpOnFail); descriptorDebug('already output help or version text: %O', context.state.didOutputHelpOrVersionText); (0, _nodeAssert.default)(!meta.hasChildren || type !== 'pure child', _error.BfErrorMessage.GuruMeditation()); const isParousParentHelperHandlingCommandNotImplementedError = meta.hasChildren && descriptor === 'helper' && (0, _error.isCommandNotImplementedError)(error); descriptorDebug('is parous parent helper handling CommandNotImplementedError: %O', isParousParentHelperHandlingCommandNotImplementedError); const showForYargsErrors = showHelpOnFail !== false && (typeof showHelpOnFail !== 'object' || (showHelpOnFail.showFor?.yargs ?? true)); const showForCliErrors = showHelpOnFail !== false && (typeof showHelpOnFail !== 'object' || (showHelpOnFail.showFor?.cli ?? false)); const showForOtherErrors = showHelpOnFail !== false && (typeof showHelpOnFail !== 'object' || (showHelpOnFail.showFor?.other ?? false)); descriptorDebug('show help text for yargs errors: %O', showForYargsErrors); descriptorDebug('show help text for CliError errors: %O', showForCliErrors); descriptorDebug('show help text for other errors: %O', showForOtherErrors); const shouldShowHelp = isParousParentHelperHandlingCommandNotImplementedError || isErrorVanillaYargsError && showForYargsErrors || isErrorCliError && (error.showHelp === 'default' && showForCliErrors || error.showHelp !== 'default' && error.showHelp) || isErrorOtherError && showForOtherErrors; descriptorDebug('will show help text: %O', shouldShowHelp); if (shouldShowHelp && !context.state.didOutputHelpOrVersionText) { const outputStyle = isErrorCliError && error.showHelp !== 'default' ? error.showHelp !== 'full' ? 'short' : 'full' : typeof showHelpOnFail === 'string' ? showHelpOnFail : (typeof showHelpOnFail === 'object' ? showHelpOnFail.outputStyle : undefined) || 'short'; descriptorDebug(`sending %O help text to stderr (triggered by %O)`, outputStyle, isErrorOtherError ? 'external' : isErrorVanillaYargsError ? 'yargs' : 'black flag'); setContextualUsageTextFor(meta.fullUsageText, outputStyle); try { programs.effector.showHelp('error'); context.state.didOutputHelpOrVersionText = true; } catch (error) { descriptorDebug.warn('effector.showHelp threw the following error (which was ignored) while attempting to generate help text for a different error: %O', error); descriptorDebug.message('will attempt to generate help text using the helper instead'); try { programs.helper.showHelp('error'); context.state.didOutputHelpOrVersionText = true; } catch (error) { descriptorDebug.warn('helper.showHelp somehow threw the following error (which was ignored) while attempting to generate help text for a different error: %O', error); descriptorDebug.message('giving up attempt to generate help text'); } } if (isParousParentHelperHandlingCommandNotImplementedError) { descriptorDebug('exited failure handler: set finalError to CliError(InvalidSubCommandInvocation)'); context.state.finalError = new _error.CliError(_error.BfErrorMessage.InvalidSubCommandInvocation()); } } if (context.state.finalError === undefined) { if (isErrorCliError) { descriptorDebug('exited failure handler: set finalError to error as-is'); context.state.finalError = error; } else { descriptorDebug('exited failure handler: set finalError to error wrapped with CliError'); context.state.finalError = new _error.CliError(error || message || _error.BfErrorMessage.GuruMeditation()); } } else { descriptorDebug('exited failure handler: finalError unchanged'); } throw context.state.finalError; }; } function setContextualUsageTextFor(fullUsageText, fullOrShort) { const updatedUsage = fullOrShort === 'short' ? fullUsageText.split('\n')[0] : fullUsageText; programs.helper.usage(null); programs.effector.usage(null); programs.helper.usage(updatedUsage); programs.effector.usage(updatedUsage); } } async function makePartiallyConfiguredProgram(descriptor) { const makerDebug = discoverDebug.extend('make:program:partial'); const deferredCommandArgs = []; makerDebug('creating new proto-%O program (awaiting full configuration)', descriptor); const { default: makeVanillaYargs } = await import('yargs/yargs'); const vanillaYargs = makeVanillaYargs(); vanillaYargs.help(false); vanillaYargs.version(false); if (descriptor === 'router') { vanillaYargs.scriptName('[router]').usage('[router]').strict(false).exitProcess(false).fail(false); } else if (context.state.globalHelpOption?.name.length) { const { name: helpOptionName, description: helpOptionDescription } = context.state.globalHelpOption; (0, _nodeAssert.default)(helpOptionName.length, _error.BfErrorMessage.GuruMeditation()); vanillaYargs.option(helpOptionName, { boolean: true, description: helpOptionDescription }); } if (descriptor === 'helper') { vanillaYargs.strict(false); } vanillaYargs.exitProcess(false); vanillaYargs.showHelpOnFail(false); return new Proxy(vanillaYargs, { get(target, property, proxy) { const isOwnProperty = Object.hasOwn(vanillaYargs, property) || Object.hasOwn(Object.getPrototypeOf(vanillaYargs), property); if (['help', 'version'].includes(property)) { return function () { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvocationNotAllowed(property)); }; } if (property === 'parseSync') { return function () { throw new _error.AssertionFailedError(_error.BfErrorMessage.UseParseAsyncInstead()); }; } if (property === 'argv') { makerDebug.warn(`discarded attempted access to disabled "${property}" magic property on %O program`, descriptor); return void 'disabled by Black Flag (use parseAsync instead)'; } if (descriptor === 'helper' && typeof property === 'string') { if (property.startsWith('strict') || property.startsWith('demand')) { return function () { makerDebug(`discarded attempted access to disabled method "${property}" on %O program`, descriptor); return proxy; }; } if (property === 'option' || property === 'options') { return function (...args) { if (!args.length) { target.options(...args); return proxy; } const options = args.length === 1 ? args[0] : { [args[0]]: args[1] || {} }; const optionsShallowClone = Object.fromEntries(Object.entries(options).map(([option, optionConfiguration]) => { if ('demandOption' in optionConfiguration) { makerDebug(`discarded attempted mutation of disabled yargs option configuration key "demandOption" (for the %O option) on %O program`, option, descriptor); } const { demandOption: _, ...rest } = optionConfiguration; return [option, rest]; })); target.options(optionsShallowClone); return proxy; }; } } if (descriptor === 'router') { if (isOwnProperty && !['parseAsync', 'command'].includes(property)) { return typeof target[property] === 'function' ? function () { throw new _error.AssertionFailedError(_error.BfErrorMessage.InvocationNotAllowed(String(property))); } : void 'disabled by Black Flag (do not access routers directly)'; } } else { if (property === 'command_deferred') { return function (...args) { makerDebug('::command call was deferred for %O program', descriptor); deferredCommandArgs.push(args); return proxy; }; } if (property === 'command_finalize_deferred') { return function () { makerDebug('began alpha-sorting deferred command calls for %O program', descriptor); deferredCommandArgs.sort(([firstCommands], [secondCommands]) => { const firstCommand = [firstCommands].flat()[0]; const secondCommand = [secondCommands].flat()[0]; return firstCommand?.startsWith('$0') ? -1 : secondCommand?.startsWith('$0') ? 1 : alphaSort.compare(String(firstCommand), String(secondCommand)); }); makerDebug('calling ::command with %O deferred argument tuples for %O program', deferredCommandArgs.length, descriptor); for (const args of deferredCommandArgs) { target.command(...args); } return proxy; }; } if (property === 'showHelpOnFail') { return function (enabled) { context.state.showHelpOnFail = enabled; return proxy; }; } } const value = target[property]; if (typeof value === 'function') { return function (...args) { const returnValue = value.apply(target, args); return (0, _types.isPromise)(returnValue) ? returnValue.then(realReturnValue => maybeReturnProxy(realReturnValue)) : maybeReturnProxy(returnValue); }; } return value; function maybeReturnProxy(returnValue) { return returnValue === target ? proxy : returnValue; } } }); } function linkChildRouterToParentRouter(childRouter, childConfig, childFullName, parentRouter) { parentRouter?.command([childConfig.name, ...childConfig.aliases], '[routed-2]', {}, async function () { const routerDebug = discoverDebug.extend('router'); const givenName = context.state.rawArgv.shift(); if (routerDebug.enabled) { const splitName = childFullName.split(' '); routerDebug.message('entered router handler function bridging %O ==> %O', splitName.slice(0, -1).join(' '), splitName.at(-1)); routerDebug('shifted given name off rawArgv: %O', givenName); routerDebug('new context.state.rawArgv: %O', context.state.rawArgv); routerDebug("relinquishing control to child command's router program"); } context.state.isHandlingVersionOption = false; await childRouter.parseAsync(context.state.rawArgv, (0, _util.wrapExecutionContext)(context)); }, [], childConfig.deprecated); if (parentRouter) { discoverDebug("linked child command %O router program to its parent command's router program", childFullName); } } function addChildCommandToParent(childConfig, childFullName, parents) { if (parents) { [parents.helper, parents.effector].forEach((parent, index) => { parent.command_deferred([childConfig.name, ...childConfig.aliases], childConfig.description, makeVanillaYargsBuilder(parent, childConfig, 'second-pass'), async _parsedArgv => { throw new _error.AssertionFailedError(_error.BfErrorMessage.GuruMeditation()); }, [], childConfig.deprecated); discoverDebug(`added an entry for child command %O to its parent command's ${index === 0 ? 'helper' : 'effector'} program`, childFullName); }); } } function makeVanillaYargsBuilder(program, config, pass) { return (vanillaYargs, helpOrVersionSet) => { const yargsDebug = discoverDebug.extend(pass === 'first-pass' ? 'helper' : 'effector'); yargsDebug(`entered wrapper builder function (${pass}) for %O`, config.name); (0, _nodeAssert.default)(pass === 'first-pass' ? context.state.firstPassArgv === undefined : context.state.firstPassArgv !== undefined, _error.BfErrorMessage.BuilderCalledOnInvalidPass(pass)); try { const blackFlagBuilderResult = typeof config.builder === 'function' ? config.builder(program, context.state.isHandlingHelpOption || context.state.isHandlingVersionOption || helpOrVersionSet, context.state.firstPassArgv) : config.builder; (0, _nodeAssert.default)(!(0, _types.isPromise)(blackFlagBuilderResult), _error.BfErrorMessage.BuilderCannotBeAsync(config.name)); if (blackFlagBuilderResult && blackFlagBuilderResult !== program) { program.options(blackFlagBuilderResult); } yargsDebug(`exited wrapper builder function (${pass}) for %O (no errors)`, config.name); } catch (error) { yargsDebug.warn(`exited wrapper builder function (${pass}) for %O (with error)`, config.name); throw error; } finally { context.state.firstPassArgv = undefined; } return vanillaYargs; }; } } function defaultCommandHandler() { throw new _error.CommandNotImplementedError(); } function replaceSpaces(str) { return str.replaceAll(/\s/g, '-'); } function isModuleNotFoundSystemError(error) { return (0, _types.isNativeError)(error) && 'code' in error && (error.code === 'ERR_MODULE_NOT_FOUND' && 'url' in error || error.code === 'MODULE_NOT_FOUND' && 'moduleName' in error); } function isUnknownFileExtensionSystemError(error) { return (0, _types.isNativeError)(error) && 'code' in error && error.code === 'ERR_UNKNOWN_FILE_EXTENSION'; } function makeRequiredPositionalsOptional(dsl) { return dsl.replaceAll(/<([^>]*)>/g, '[$1]'); }