UNPKG

@pnp/cli-microsoft365

Version:

Manage Microsoft 365 and SharePoint Framework projects on any platform

920 lines 35.8 kB
import Configstore from 'configstore'; import fs from 'fs'; import { createRequire } from 'module'; import os from 'os'; import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import yargs from 'yargs-parser'; import { ZodError } from 'zod'; import Command, { CommandError } from '../Command.js'; import config from '../config.js'; import request from '../request.js'; import { settingsNames } from '../settingsNames.js'; import { telemetry } from '../telemetry.js'; import { app } from '../utils/app.js'; import { browserUtil } from '../utils/browserUtil.js'; import { formatting } from '../utils/formatting.js'; import { md } from '../utils/md.js'; import { prompt } from '../utils/prompt.js'; import { validation } from '../utils/validation.js'; import { zod } from '../utils/zod.js'; import { timings } from './timings.js'; const require = createRequire(import.meta.url); const __dirname = fileURLToPath(new URL('.', import.meta.url)); let _config; const commands = []; /** * Command to execute */ let commandToExecute; /** * Name of the command specified through args */ let currentCommandName; let optionsFromArgs; const defaultHelpMode = 'options'; const defaultHelpTarget = 'console'; const helpModes = ['options', 'examples', 'remarks', 'permissions', 'response', 'full']; const helpTargets = ['console', 'web']; const yargsConfiguration = { 'parse-numbers': true, 'strip-aliased': true, 'strip-dashed': true, 'dot-notation': false, 'boolean-negation': true, 'camel-case-expansion': false }; function getConfig() { if (!_config) { _config = new Configstore(config.configstoreName); } return _config; } function getSettingWithDefaultValue(settingName, defaultValue) { const configuredValue = cli.getConfig().get(settingName); if (typeof configuredValue === 'undefined') { return defaultValue; } else { return configuredValue; } } function getClientId() { return cli.getSettingWithDefaultValue(settingsNames.clientId, process.env.CLIMICROSOFT365_ENTRAAPPID); } function getTenant() { return cli.getSettingWithDefaultValue(settingsNames.tenantId, process.env.CLIMICROSOFT365_TENANT || 'common'); } async function execute(rawArgs) { const start = process.hrtime.bigint(); // for completion commands we also need information about commands' options const loadAllCommandInfo = rawArgs.indexOf('completion') > -1; cli.loadAllCommandsInfo(loadAllCommandInfo); // check if help for a specific command has been requested using the // 'm365 help xyz' format. If so, remove 'help' from the array of words // to use lazy loading commands but keep track of the fact that help should // be displayed let showHelp = false; if (rawArgs.length > 0 && rawArgs[0] === 'help') { showHelp = true; rawArgs.shift(); } // parse args to see if a command has been specified const parsedArgs = yargs(rawArgs); // load command await cli.loadCommandFromArgs(parsedArgs._); if (cli.commandToExecute) { // we have found a command to execute. Parse args again taking into // account short and long options, option types and whether the command // supports known and unknown options or not try { cli.optionsFromArgs = { options: getCommandOptionsFromArgs(rawArgs, cli.commandToExecute) }; } catch (e) { return cli.closeWithError(e.message, { options: parsedArgs }, false); } } else { // we need this to properly support displaying commands // from the current group cli.optionsFromArgs = { options: parsedArgs }; } // show help if no match found, help explicitly requested or // no command specified if (!cli.commandToExecute || showHelp || parsedArgs.h || parsedArgs.help) { if (parsedArgs.output !== 'none') { await printHelp(await getHelpMode(parsedArgs)); } return; } delete cli.optionsFromArgs.options._; delete cli.optionsFromArgs.options['--']; try { // replace values staring with @ with file contents loadOptionValuesFromFiles(cli.optionsFromArgs); } catch (e) { return cli.closeWithError(e, cli.optionsFromArgs); } const startProcessing = process.hrtime.bigint(); try { // process options before passing them on to validation stage const contextCommandOptions = await loadOptionsFromContext(cli.commandToExecute.options, cli.optionsFromArgs.options.debug); cli.optionsFromArgs.options = { ...contextCommandOptions, ...cli.optionsFromArgs.options }; await cli.commandToExecute.command.processOptions(cli.optionsFromArgs.options); const endProcessing = process.hrtime.bigint(); timings.options.push(Number(endProcessing - startProcessing)); } catch (e) { const endProcessing = process.hrtime.bigint(); timings.options.push(Number(endProcessing - startProcessing)); return cli.closeWithError(e.message, cli.optionsFromArgs, false); } // if output not specified, set the configured output value (if any) if (cli.optionsFromArgs.options.output === undefined) { cli.optionsFromArgs.options.output = cli.getSettingWithDefaultValue(settingsNames.output, 'json'); } let finalArgs = cli.optionsFromArgs.options; if (cli.commandToExecute?.command.schema) { while (true) { const startValidation = process.hrtime.bigint(); const result = cli.commandToExecute.command.getSchemaToParse().safeParse(cli.optionsFromArgs.options); const endValidation = process.hrtime.bigint(); timings.validation.push(Number(endValidation - startValidation)); if (result.success) { finalArgs = result.data; break; } else { const hasNonRequiredErrors = result.error.errors.some(e => e.code !== 'invalid_type' || e.received !== 'undefined'); const shouldPrompt = cli.getSettingWithDefaultValue(settingsNames.prompt, true); if (hasNonRequiredErrors === false && shouldPrompt) { await cli.error('🌶️ Provide values for the following parameters:'); for (const error of result.error.errors) { const optionName = error.path.join('.'); const optionInfo = cli.commandToExecute.options.find(o => o.name === optionName); const answer = await cli.promptForValue(optionInfo); // coerce the answer to the correct type try { const parsed = getCommandOptionsFromArgs([`--${optionName}`, answer], cli.commandToExecute); cli.optionsFromArgs.options[optionName] = parsed[optionName]; } catch (e) { return cli.closeWithError(e.message, cli.optionsFromArgs, true); } } } else { result.error.errors.forEach(e => { if (e.code === 'invalid_type' && e.received === 'undefined') { e.message = `Required option not specified`; } }); return cli.closeWithError(result.error, cli.optionsFromArgs, true); } } } } else { const startValidation = process.hrtime.bigint(); const validationResult = await cli.commandToExecute.command.validate(cli.optionsFromArgs, cli.commandToExecute); const endValidation = process.hrtime.bigint(); timings.validation.push(Number(endValidation - startValidation)); if (validationResult !== true) { return cli.closeWithError(validationResult, cli.optionsFromArgs, true); } } const end = process.hrtime.bigint(); timings.core.push(Number(end - start)); try { await cli.executeCommand(cli.commandToExecute.command, { options: finalArgs }); const endTotal = process.hrtime.bigint(); timings.total.push(Number(endTotal - start)); await printTimings(rawArgs); process.exit(0); } catch (err) { const endTotal = process.hrtime.bigint(); timings.total.push(Number(endTotal - start)); await printTimings(rawArgs); await cli.closeWithError(err, cli.optionsFromArgs); /* c8 ignore next */ } } async function printTimings(rawArgs) { if (rawArgs.some(arg => arg === '--debug')) { await cli.error(''); await cli.error('Timings:'); Object.getOwnPropertyNames(timings).forEach(async (key) => { await cli.error(`${key}: ${timings[key].reduce((a, b) => a + b, 0) / 1e6}ms`); }); } } async function executeCommand(command, args) { const logger = { log: async (message) => { if (args.options.output !== 'none') { const output = await cli.formatOutput(command, message, args.options); cli.log(output); } }, logRaw: async (message) => { if (args.options.output !== 'none') { cli.log(message); } }, logToStderr: async (message) => { if (args.options.output !== 'none') { await cli.error(message); } } }; if (args.options.debug) { await logger.logToStderr(`Executing command ${command.name} with options ${JSON.stringify(args)}`); } // store the current command name, if any and set the name to the name of // the command to execute const parentCommandName = cli.currentCommandName; cli.currentCommandName = command.getCommandName(cli.currentCommandName); const startCommand = process.hrtime.bigint(); try { await command.action(logger, args); if (args.options.debug || args.options.verbose) { const chalk = (await import('chalk')).default; await logger.logToStderr(chalk.green('DONE')); } } finally { // restore the original command name cli.currentCommandName = parentCommandName; const endCommand = process.hrtime.bigint(); timings.command.push(Number(endCommand - startCommand)); } } async function executeCommandWithOutput(command, args, listener) { const log = []; const logErr = []; const logger = { log: async (message) => { const formattedMessage = await cli.formatOutput(command, message, args.options); if (listener && listener.stdout) { listener.stdout(formattedMessage); } log.push(formattedMessage); }, logRaw: async (message) => { const formattedMessage = await cli.formatOutput(command, message, args.options); if (listener && listener.stdout) { listener.stdout(formattedMessage); } log.push(formattedMessage); }, logToStderr: async (message) => { if (listener && listener.stderr) { listener.stderr(message); } logErr.push(message); } }; if (args.options.debug && args.options.output !== 'none') { const message = `Executing command ${command.name} with options ${JSON.stringify(args)}`; if (listener && listener.stderr) { listener.stderr(message); } logErr.push(message); } // store the current command name, if any and set the name to the name of // the command to execute const parentCommandName = cli.currentCommandName; cli.currentCommandName = command.getCommandName(); // store the current logger if any const currentLogger = request.logger; try { await command.action(logger, args); return ({ stdout: log.join(os.EOL), stderr: logErr.join(os.EOL) }); } catch (err) { // restoring the command and logger is done here instead of in a 'finally' because there were issues with the code coverage tool // restore the original command name cli.currentCommandName = parentCommandName; // restore the original logger request.logger = currentLogger; throw { error: err, stderr: logErr.join(os.EOL) }; } /* c8 ignore next */ finally { // restore the original command name cli.currentCommandName = parentCommandName; // restore the original logger request.logger = currentLogger; } } function loadAllCommandsInfo(loadFull = false) { const commandsInfoFileName = loadFull ? 'allCommandsFull.json' : 'allCommands.json'; cli.commands = require(path.join(__dirname, '..', '..', commandsInfoFileName)); } /** * Loads command files into CLI based on the specified arguments. * * @param commandNameWords Array of words specified as args */ async function loadCommandFromArgs(commandNameWords) { if (commandNameWords.length === 0) { return; } cli.currentCommandName = commandNameWords.join(' '); const commandFilePath = cli.commands .find(c => c.name === cli.currentCommandName || c.aliases?.find(a => a === cli.currentCommandName))?.file ?? ''; if (commandFilePath) { await cli.loadCommandFromFile(commandFilePath); } } async function loadOptionsFromContext(commandOptions, debug) { const filePath = '.m365rc.json'; let m365rc = {}; if (!fs.existsSync(filePath)) { return; } if (debug) { await cli.error('found .m365rc.json file'); } try { const fileContents = fs.readFileSync(filePath, 'utf8'); if (fileContents) { m365rc = JSON.parse(fileContents); } } catch (e) { await cli.closeWithError(`Error parsing ${filePath}`, { options: {} }); /* c8 ignore next */ } if (!m365rc.context) { return; } if (debug) { await cli.error('found context in .m365rc.json file'); } const context = m365rc.context; const foundOptions = {}; await commandOptions.forEach(async (option) => { if (context[option.name]) { foundOptions[option.name] = context[option.name]; if (debug) { await cli.error(`returning ${option.name} option from context`); } } }); return foundOptions; } /** * Loads command from the specified file into CLI. If can't find the file * or the file doesn't contain a command, loads all available commands. * * @param commandFilePathUrl File path of the file with command to load */ async function loadCommandFromFile(commandFileUrl) { const commandsFolder = path.join(__dirname, '../m365'); const filePath = path.join(commandsFolder, commandFileUrl); if (!fs.existsSync(filePath)) { // reset command name cli.currentCommandName = undefined; return; } try { const command = await import(pathToFileURL(filePath).toString()); if (command.default instanceof Command) { const commandInfo = cli.commands.find(c => c.file === commandFileUrl); cli.commandToExecute = cli.getCommandInfo(command.default, commandFileUrl, commandInfo?.help); } } catch { } } function getCommandInfo(command, filePath = '', helpFilePath = '') { const options = command.schema ? zod.schemaToOptionInfo(command.schema) : getCommandOptions(command); command.optionsInfo = options; return { aliases: command.alias(), name: command.name, description: command.description, command: command, options, defaultProperties: command.defaultProperties(), file: filePath, help: helpFilePath }; } function getCommandOptions(command) { const options = []; command.options.forEach(option => { const required = option.option.indexOf('<') > -1; const optionArgs = option.option.split(/[ ,|]+/); let short; let long; let name = ''; optionArgs.forEach(o => { if (o.startsWith('--')) { long = o.replace('--', ''); name = long; } else if (o.startsWith('-')) { short = o.replace('-', ''); name = short; } }); options.push({ autocomplete: option.autocomplete, long: long, name: name, required: required, short: short }); }); return options; } function getCommandOptionsFromArgs(args, commandInfo) { const yargsOptions = { alias: {}, configuration: yargsConfiguration }; let argsToParse = args; if (commandInfo) { if (commandInfo.command.schema) { yargsOptions.string = commandInfo.options.filter(o => o.type === 'string').map(o => o.name); yargsOptions.boolean = commandInfo.options.filter(o => o.type === 'boolean').map(o => o.name); yargsOptions.number = commandInfo.options.filter(o => o.type === 'number').map(o => o.name); argsToParse = getRewrittenArgs(args, yargsOptions.boolean); } else { const commandTypes = commandInfo.command.types; if (commandTypes) { yargsOptions.string = commandTypes.string; // minimist will parse unused boolean options to 'false' (unused options => options that are not included in the args) // But in the CLI booleans are nullable. They can can be true, false or undefined. // For this reason we only pass boolean types that are actually used as arg. yargsOptions.boolean = commandTypes.boolean.filter(optionName => args.some(arg => `--${optionName}` === arg || `-${optionName}` === arg)); } argsToParse = getRewrittenArgs(args, commandTypes.boolean); } commandInfo.options.forEach(option => { if (option.short && option.long) { yargsOptions.alias[option.long] = option.short; } }); } return yargs(argsToParse, yargsOptions); } /** * Rewrites arguments (if necessary) before passing them into minimist. * Currently only boolean values are checked and fixed. * Args are only checked and rewritten if the option has been added to the 'types.boolean' array. */ function getRewrittenArgs(args, booleanTypes) { if (booleanTypes.length === 0) { return args; } return args.map((arg, index, array) => { if (arg.startsWith('-') || index === 0) { return arg; } // This line checks if the current arg is a value that belongs to a boolean option. if (booleanTypes.some(t => `--${t}` === array[index - 1] || `-${t}` === array[index - 1])) { const rewrittenBoolean = formatting.rewriteBooleanValue(arg); if (!validation.isValidBoolean(rewrittenBoolean)) { const optionName = array[index - 1]; throw new Error(`The value '${arg}' for option '${optionName}' is not a valid boolean`); } return rewrittenBoolean; } return arg; }); } async function formatOutput(command, logStatement, options) { if (logStatement instanceof Date) { return logStatement.toString(); } let logStatementType = typeof logStatement; if (logStatementType === 'undefined') { return logStatement; } // we need to get the list of object's properties to see if the specified // JMESPath query (if any) filters object's properties or not. We need to // know this in order to decide if we should use default command's // properties or custom ones from JMESPath const originalObject = Array.isArray(logStatement) ? getFirstNonUndefinedArrayItem(logStatement) : logStatement; const originalProperties = originalObject && typeof originalObject !== 'string' ? Object.getOwnPropertyNames(originalObject) : []; if (options.query && !options.help) { const jmespath = await import('jmespath'); try { logStatement = jmespath.search(logStatement, options.query); } catch (e) { const message = `JMESPath query error. ${e.message}. See https://jmespath.org/specification.html for more information`; await cli.closeWithError(message, { options }, false); /* c8 ignore next */ } // we need to update the statement type in case the JMESPath query // returns an object of different shape than the original message to log // #2095 logStatementType = typeof logStatement; } if (!options.output || options.output === 'json') { return command.getJsonOutput(logStatement); } if (logStatement instanceof CommandError) { const chalk = (await import('chalk')).default; return chalk.red(`Error: ${logStatement.message}`); } let arrayType = ''; if (!Array.isArray(logStatement)) { logStatement = [logStatement]; arrayType = logStatementType; } else { for (let i = 0; i < logStatement.length; i++) { if (Array.isArray(logStatement[i])) { arrayType = 'array'; break; } const t = typeof logStatement[i]; if (t !== 'undefined') { arrayType = t; break; } } } if (arrayType !== 'object') { return logStatement.join(os.EOL); } // if output type has been set to 'text', process the retrieved // data so that returned objects contain only default properties specified // on the current command. If there is no current command or the // command doesn't specify default properties, return original data if (cli.shouldTrimOutput(options.output)) { const currentCommand = cli.commandToExecute; if (arrayType === 'object' && currentCommand && currentCommand.defaultProperties) { // the log statement contains the same properties as the original object // so it can be filtered following the default properties specified on // the command if (JSON.stringify(originalProperties) === JSON.stringify(Object.getOwnPropertyNames(logStatement[0]))) { // in some cases we return properties wrapped in `value` array // returned by the API. We'll remove it in the future, but for now // we'll use a workaround to drop the `value` array here if (logStatement[0].value && Array.isArray(logStatement[0].value)) { logStatement = logStatement[0].value; } logStatement = logStatement.map((s) => formatting.filterObject(s, currentCommand.defaultProperties)); } } } switch (options.output) { case 'csv': return command.getCsvOutput(logStatement, options); case 'md': return command.getMdOutput(logStatement, command, options); default: return command.getTextOutput(logStatement); } } function getFirstNonUndefinedArrayItem(arr) { for (let i = 0; i < arr.length; i++) { const a = arr[i]; if (typeof a !== 'undefined') { return a; } } return undefined; } async function printHelp(helpMode, exitCode = 0) { const properties = {}; if (cli.commandToExecute) { properties.command = cli.commandToExecute.name; const helpTarget = getSettingWithDefaultValue(settingsNames.helpTarget, defaultHelpTarget); if (helpTarget === 'web') { await openHelpInBrowser(); } else { printCommandHelp(helpMode); } } else { if (cli.currentCommandName && !cli.commands.some(command => command.name.startsWith(cli.currentCommandName))) { const chalk = (await import('chalk')).default; await cli.error(chalk.red(`Command '${cli.currentCommandName}' was not found. Below you can find the commands and command groups you can use. For detailed information on a command group, use 'm365 [command group] --help'.`)); } cli.log(); cli.log(`CLI for Microsoft 365 v${app.packageJson().version}`); cli.log(`${app.packageJson().description} `); cli.log(); properties.command = 'commandList'; cli.printAvailableCommands(); } await telemetry.trackEvent('help', properties); process.exit(exitCode); } async function openHelpInBrowser() { const pathChunks = cli.commandToExecute.help?.replace(/\\/g, '/').replace(/\.[^/.]+$/, ''); const onlineUrl = `https://pnp.github.io/cli-microsoft365/cmd/${pathChunks}`; await browserUtil.open(onlineUrl); } function printCommandHelp(helpMode) { const docsRootDir = path.join(__dirname, '..', '..', 'docs'); const helpFilePath = path.join(docsRootDir, 'docs', 'cmd', cli.commandToExecute.help); if (fs.existsSync(helpFilePath)) { let helpContents = fs.readFileSync(helpFilePath, 'utf8'); helpContents = getHelpSection(helpMode, helpContents); helpContents = md.md2plain(helpContents, docsRootDir); cli.log(); cli.log(helpContents); } } async function getHelpMode(options) { const { h, help } = options; if (!h && !help) { return cli.getSettingWithDefaultValue(settingsNames.helpMode, defaultHelpMode); } // user passed -h or --help, let's see if they passed a specific mode // or requested the default const helpMode = h ?? help; if (typeof helpMode === 'boolean' || typeof helpMode !== 'string') { // requested default mode or passed a number, let's use default return cli.getSettingWithDefaultValue(settingsNames.helpMode, defaultHelpMode); } else { const lowerCaseHelpMode = helpMode.toLowerCase(); if (cli.helpModes.indexOf(lowerCaseHelpMode) < 0) { await cli.closeWithError(`Unknown help mode ${helpMode}. Allowed values are ${cli.helpModes.join(', ')} `, { options }, false); /* c8 ignore next 2 */ return ''; // noop } else { return lowerCaseHelpMode; } } } function getHelpSection(helpMode, helpContents) { if (helpMode === 'full') { return helpContents; } // options is the first section, so get help up to options const titleAndUsage = helpContents.substring(0, helpContents.indexOf('## Options')); // find the requested section const sectionLines = []; const sectionName = helpMode[0].toUpperCase() + helpMode.substring(1); const lines = helpContents.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.indexOf(`## ${sectionName}`) === 0) { sectionLines.push(line); } else if (sectionLines.length > 0) { if (line.indexOf('## ') === 0) { // we've reached the next section, stop break; } else { sectionLines.push(line); } } } return titleAndUsage + sectionLines.join('\n'); } function printAvailableCommands() { // commands that match the current group const commandsToPrint = {}; // sub-commands in the current group const commandGroupsToPrint = {}; // current command group, eg. 'spo', 'spo site' let currentGroup = ''; const addToList = (commandName, command) => { const pos = commandName.indexOf(' ', currentGroup.length + 1); if (pos === -1) { commandsToPrint[commandName] = command; } else { const subCommandsGroup = commandName.substring(0, pos); if (!commandGroupsToPrint[subCommandsGroup]) { commandGroupsToPrint[subCommandsGroup] = 0; } commandGroupsToPrint[subCommandsGroup]++; } }; // get current command group if (cli.optionsFromArgs && cli.optionsFromArgs.options && cli.optionsFromArgs.options._ && cli.optionsFromArgs.options._.length > 0) { currentGroup = cli.optionsFromArgs.options._.join(' '); if (currentGroup) { currentGroup += ' '; } } const getCommandsForGroup = () => { for (let i = 0; i < cli.commands.length; i++) { const command = cli.commands[i]; if (command.name.startsWith(currentGroup)) { addToList(command.name, command); } if (command.aliases) { for (let j = 0; j < command.aliases.length; j++) { const alias = command.aliases[j]; if (alias.startsWith(currentGroup)) { addToList(alias, command); } } } } }; getCommandsForGroup(); if (Object.keys(commandsToPrint).length === 0 && Object.keys(commandGroupsToPrint).length === 0) { // specified string didn't match any commands. Reset group and try again currentGroup = ''; getCommandsForGroup(); } const namesOfCommandsToPrint = Object.keys(commandsToPrint); if (namesOfCommandsToPrint.length > 0) { // determine the length of the longest command name to pad strings + ' [options]' const maxLength = Math.max(...namesOfCommandsToPrint.map(s => s.length)) + 10; cli.log(`Commands:`); cli.log(); const sortedCommandNamesToPrint = Object.getOwnPropertyNames(commandsToPrint).sort(); sortedCommandNamesToPrint.forEach(commandName => { cli.log(` ${`${commandName} [options]`.padEnd(maxLength, ' ')} ${commandsToPrint[commandName].description}`); }); } const namesOfCommandGroupsToPrint = Object.keys(commandGroupsToPrint); if (namesOfCommandGroupsToPrint.length > 0) { if (namesOfCommandsToPrint.length > 0) { cli.log(); } // determine the longest command group name to pad strings + ' *' const maxLength = Math.max(...namesOfCommandGroupsToPrint.map(s => s.length)) + 2; cli.log(`Commands groups:`); cli.log(); // sort commands groups (because of aliased commands) const sortedCommandGroupsToPrint = Object .keys(commandGroupsToPrint) .sort() .reduce((object, key) => { object[key] = commandGroupsToPrint[key]; return object; }, {}); for (const commandGroup in sortedCommandGroupsToPrint) { cli.log(` ${`${commandGroup} *`.padEnd(maxLength, ' ')} ${commandGroupsToPrint[commandGroup]} command${commandGroupsToPrint[commandGroup] === 1 ? '' : 's'}`); } } cli.log(); } async function closeWithError(error, args, showHelpIfEnabled = false) { let exitCode = 1; if (args.options.output === 'none') { return process.exit(exitCode); } let errorMessage = error instanceof CommandError ? error.message : error; if (error instanceof ZodError) { errorMessage = error.errors.map(e => (e.path.length > 0 ? `${e.path.join('.')}: ${e.message}` : e.message)).join(os.EOL); } if ((!args.options.output || args.options.output === 'json') && !cli.getSettingWithDefaultValue(settingsNames.printErrorsAsPlainText, true)) { errorMessage = JSON.stringify({ error: errorMessage }); } else { const chalk = (await import('chalk')).default; errorMessage = chalk.red(`Error: ${errorMessage}`); } if (error instanceof CommandError && error.code) { exitCode = error.code; } await cli.error(errorMessage); if (showHelpIfEnabled && cli.getSettingWithDefaultValue(settingsNames.showHelpOnFailure, showHelpIfEnabled)) { await cli.error(`Run 'm365 ${cli.commandToExecute.name} -h' for help.`); } process.exit(exitCode); // will never be run. Required for testing where we're stubbing process.exit /* c8 ignore next */ throw new Error(errorMessage); /* c8 ignore next */ } function log(message, ...optionalParams) { if (message) { console.log(message, ...optionalParams); } else { console.log(); } } async function error(message, ...optionalParams) { const errorOutput = cli.getSettingWithDefaultValue(settingsNames.errorOutput, 'stderr'); if (errorOutput === 'stdout') { console.log(message, ...optionalParams); } else { console.error(message, ...optionalParams); } } async function promptForValue(optionInfo) { return optionInfo.autocomplete !== undefined ? await prompt.forSelection({ message: `${optionInfo.name}: `, choices: optionInfo.autocomplete.map((choice) => { return { name: choice, value: choice }; }) }) : await prompt.forInput({ message: `${optionInfo.name}: ` }); } async function promptForSelection(config) { const answer = await prompt.forSelection(config); await cli.error(''); return answer; } async function promptForConfirmation(config) { const answer = await prompt.forConfirmation(config); await cli.error(''); return answer; } async function promptForInput(config) { const answer = await prompt.forInput(config); await cli.error(''); return answer; } async function handleMultipleResultsFound(message, values) { const prompt = cli.getSettingWithDefaultValue(settingsNames.prompt, true); if (!prompt) { throw new Error(`${message} Found: ${Object.keys(values).join(', ')}.`); } await cli.error(`🌶️ ${message} `); const choices = Object.keys(values).map((choice) => { return { name: choice, value: choice }; }); const response = await cli.promptForSelection({ message: `Please choose one:`, choices }); return values[response]; } function loadOptionValuesFromFiles(args) { const optionNames = Object.getOwnPropertyNames(args.options); optionNames.forEach(option => { const value = args.options[option]; if (!value || typeof value !== 'string' || !value.startsWith('@')) { return; } const filePath = value.substring(1); // if the file doesn't exist, leave as-is, if it exists replace with // contents from the file if (fs.existsSync(filePath)) { args.options[option] = fs.readFileSync(filePath, 'utf-8'); } }); } function shouldTrimOutput(output) { return output === 'text'; } export const cli = { closeWithError, commands, commandToExecute, getClientId, getConfig, getTenant, currentCommandName, error, execute, executeCommand, executeCommandWithOutput, formatOutput, getCommandInfo, getSettingWithDefaultValue, handleMultipleResultsFound, helpModes, helpTargets, loadAllCommandsInfo, loadCommandFromArgs, loadCommandFromFile, log, optionsFromArgs, printAvailableCommands, promptForConfirmation, promptForInput, promptForSelection, promptForValue, shouldTrimOutput, yargsConfiguration }; //# sourceMappingURL=cli.js.map