UNPKG

@oclif/core

Version:

base library for oclif CLIs

337 lines (336 loc) 12.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Command = void 0; const node_url_1 = require("node:url"); const node_util_1 = require("node:util"); const cache_1 = __importDefault(require("./cache")); const config_1 = require("./config"); const Errors = __importStar(require("./errors")); const util_1 = require("./help/util"); const logger_1 = require("./logger"); const Parser = __importStar(require("./parser")); const aggregate_flags_1 = require("./util/aggregate-flags"); const ids_1 = require("./util/ids"); const util_2 = require("./util/util"); const ux_1 = require("./ux"); const pjson = cache_1.default.getInstance().get('@oclif/core'); /** * swallows stdout epipe errors * this occurs when stdout closes such as when piping to head */ process.stdout.on('error', (err) => { if (err && err.code === 'EPIPE') return; throw err; }); /** * An abstract class which acts as the base for each command * in your project. */ class Command { argv; config; static _base = `${pjson.name}@${pjson.version}`; /** An array of aliases for this command. */ static aliases = []; /** An order-dependent object of arguments for the command */ static args = {}; static baseFlags; /** * Emit deprecation warning when a command alias is used */ static deprecateAliases; static deprecationOptions; /** * A full description of how to use the command. * * If no summary, the first line of the description will be used as the summary. */ static description; static enableJsonFlag = false; /** * An array of examples to show at the end of the command's help. * * IF only a string is provided, it will try to look for a line that starts * with the cmd.bin as the example command and the rest as the description. * If found, the command will be formatted appropriately. * * ``` * EXAMPLES: * A description of a particular use case. * * $ <%= config.bin => command flags * ``` */ static examples; /** A hash of flags for the command */ static flags; static hasDynamicHelp = false; static help; /** Hide the command from help */ static hidden; /** An array of aliases for this command that are hidden from help. */ static hiddenAliases = []; /** A command ID, used mostly in error or verbose reporting. */ static id; static plugin; static pluginAlias; static pluginName; static pluginType; /** Mark the command as a given state (e.g. beta or deprecated) in help */ static state; /** When set to false, allows a variable amount of arguments */ static strict = true; /** * The tweet-sized description for your class, used in a parent-commands * sub-command listing and as the header for the command help. */ static summary; /** * An override string (or strings) for the default usage documentation. */ static usage; debug; id; constructor(argv, config) { this.argv = argv; this.config = config; this.id = this.ctor.id; try { this.debug = (0, logger_1.makeDebug)(this.id ? `${this.config.bin}:${this.id}` : this.config.bin); } catch { this.debug = () => { // noop }; } } /** * instantiate and run the command * * @param {Command.Class} this - the command class * @param {string[]} argv argv * @param {LoadOptions} opts options * @returns {Promise<unknown>} result */ static async run(argv, opts) { if (!argv) argv = process.argv.slice(2); // Handle the case when a file URL string is passed in such as 'import.meta.url'; covert to file path. if (typeof opts === 'string' && opts.startsWith('file://')) { opts = (0, node_url_1.fileURLToPath)(opts); } const config = await config_1.Config.load(opts || require.main?.filename || __dirname); const cache = cache_1.default.getInstance(); if (!cache.has('config')) cache.set('config', config); const cmd = new this(argv, config); if (!cmd.id) { const id = cmd.constructor.name.toLowerCase(); cmd.id = id; cmd.ctor.id = id; } return cmd._run(); } get ctor() { return this.constructor; } async _run() { let err; let result; try { // remove redirected env var to allow subsessions to run autoupdated client this.removeEnvVar('REDIRECTED'); await this.init(); result = await this.run(); } catch (error) { err = error; await this.catch(error); } finally { await this.finally(err); } if (result && this.jsonEnabled()) this.logJson(this.toSuccessJson(result)); return result; } async catch(err) { process.exitCode = process.exitCode ?? err.exitCode ?? 1; if (this.jsonEnabled()) { this.logJson(this.toErrorJson(err)); } else { if (!err.message) throw err; try { ux_1.ux.action.stop(ux_1.ux.colorize('bold', ux_1.ux.colorize('red', '!'))); } catch { } throw err; } } error(input, options = {}) { return Errors.error(input, options); } exit(code = 0) { Errors.exit(code); } async finally(_) { } async init() { this.debug('init version: %s argv: %o', this.ctor._base, this.argv); const g = globalThis; g['http-call'] = g['http-call'] || {}; g['http-call'].userAgent = this.config.userAgent; this.warnIfCommandDeprecated(); } /** * Determine if the command is being run with the --json flag in a command that supports it. * * @returns {boolean} true if the command supports json and the --json flag is present */ jsonEnabled() { // If the command doesn't support json, return false if (!this.ctor.enableJsonFlag) return false; // If the CONTENT_TYPE env var is set to json, return true if (this.config.scopedEnvVar?.('CONTENT_TYPE')?.toLowerCase() === 'json') return true; const passThroughIndex = this.argv.indexOf('--'); const jsonIndex = this.argv.indexOf('--json'); return passThroughIndex === -1 ? // If '--' is not present, then check for `--json` in this.argv jsonIndex !== -1 : // If '--' is present, return true only the --json flag exists and is before the '--' jsonIndex !== -1 && jsonIndex < passThroughIndex; } log(message = '', ...args) { if (!this.jsonEnabled()) { message = typeof message === 'string' ? message : (0, node_util_1.inspect)(message); ux_1.ux.stdout(message, ...args); } } logJson(json) { ux_1.ux.stdout(ux_1.ux.colorizeJson(json, { pretty: true, theme: this.config.theme?.json })); } logToStderr(message = '', ...args) { if (!this.jsonEnabled()) { message = typeof message === 'string' ? message : (0, node_util_1.inspect)(message); ux_1.ux.stderr(message, ...args); } } async parse(options, argv = this.argv) { if (!options) options = this.ctor; const opts = { context: this, ...options, flags: (0, aggregate_flags_1.aggregateFlags)(options.flags, options.baseFlags, options.enableJsonFlag), }; const hookResult = await this.config.runHook('preparse', { argv: [...argv], options: opts }); // Since config.runHook will only run the hook for the root plugin, hookResult.successes will always have a length of 0 or 1 // But to be extra safe, we find the result that matches the root plugin. const argvToParse = hookResult.successes?.length ? (hookResult.successes.find((s) => s.plugin.root === cache_1.default.getInstance().get('rootPlugin')?.root)?.result ?? argv) : argv; this.argv = [...argvToParse]; const results = await Parser.parse(argvToParse, opts); this.warnIfFlagDeprecated(results.flags ?? {}); return results; } toErrorJson(err) { return { error: err }; } toSuccessJson(result) { return result; } warn(input) { if (!this.jsonEnabled()) Errors.warn(input); return input; } warnIfCommandDeprecated() { const [id] = (0, util_1.normalizeArgv)(this.config); if (this.ctor.deprecateAliases && this.ctor.aliases.includes(id)) { const cmdName = (0, ids_1.toConfiguredId)(this.ctor.id, this.config); const aliasName = (0, ids_1.toConfiguredId)(id, this.config); this.warn((0, util_1.formatCommandDeprecationWarning)(aliasName, { to: cmdName })); } if (this.ctor.state === 'deprecated') { const cmdName = (0, ids_1.toConfiguredId)(this.ctor.id, this.config); this.warn((0, util_1.formatCommandDeprecationWarning)(cmdName, this.ctor.deprecationOptions)); } } warnIfFlagDeprecated(flags) { const allFlags = (0, aggregate_flags_1.aggregateFlags)(this.ctor.flags, this.ctor.baseFlags, this.ctor.enableJsonFlag); for (const flag of Object.keys(flags)) { const flagDef = allFlags[flag]; const deprecated = flagDef?.deprecated; if (deprecated) { this.warn((0, util_1.formatFlagDeprecationWarning)(flag, deprecated)); } const deprecateAliases = flagDef?.deprecateAliases; if (deprecateAliases) { const aliases = (0, util_2.uniq)([...(flagDef?.aliases ?? []), ...(flagDef?.charAliases ?? [])]).map((a) => a.length === 1 ? `-${a}` : `--${a}`); if (aliases.length === 0) return; const foundAliases = aliases.filter((alias) => this.argv.includes(alias)); for (const alias of foundAliases) { let preferredUsage = `--${flagDef?.name}`; if (flagDef?.char) { preferredUsage += ` | -${flagDef?.char}`; } this.warn((0, util_1.formatFlagDeprecationWarning)(alias, { to: preferredUsage })); } } } } removeEnvVar(envVar) { const keys = []; try { keys.push(...this.config.scopedEnvVarKeys(envVar)); } catch { keys.push(this.config.scopedEnvVarKey(envVar)); } keys.map((key) => delete process.env[key]); } } exports.Command = Command;