@oclif/core
Version:
base library for oclif CLIs
817 lines (816 loc) • 35.3 kB
JavaScript
"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.Config = void 0;
const ejs = __importStar(require("ejs"));
const is_wsl_1 = __importDefault(require("is-wsl"));
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const node_url_1 = require("node:url");
const cache_1 = __importDefault(require("../cache"));
const errors_1 = require("../errors");
const util_1 = require("../help/util");
const logger_1 = require("../logger");
const module_loader_1 = require("../module-loader");
const performance_1 = require("../performance");
const settings_1 = require("../settings");
const determine_priority_1 = require("../util/determine-priority");
const fs_1 = require("../util/fs");
const ids_1 = require("../util/ids");
const os_1 = require("../util/os");
const util_2 = require("../util/util");
const ux_1 = require("../ux");
const theme_1 = require("../ux/theme");
const plugin_loader_1 = __importDefault(require("./plugin-loader"));
const ts_path_1 = require("./ts-path");
const util_3 = require("./util");
const debug = (0, util_3.makeDebug)();
const _pjson = cache_1.default.getInstance().get('@oclif/core');
const BASE = `${_pjson.name}@${_pjson.version}`;
const ROOT_ONLY_HOOKS = new Set(['preparse']);
function displayWarnings() {
if (process.listenerCount('warning') > 1)
return;
process.on('warning', (warning) => {
console.error(warning.stack);
if (warning.detail)
console.error(warning.detail);
});
}
function channelFromVersion(version) {
const m = version.match(/[^-]+(?:-([^.]+))?/);
return (m && m[1]) || 'stable';
}
function isConfig(o) {
return o && Boolean(o._base);
}
class Permutations extends Map {
validPermutations = new Map();
add(permutation, commandId) {
this.validPermutations.set(permutation, commandId);
for (const id of (0, util_3.collectUsableIds)([permutation])) {
if (this.has(id)) {
this.set(id, this.get(id).add(commandId));
}
else {
this.set(id, new Set([commandId]));
}
}
}
get(key) {
return super.get(key) ?? new Set();
}
getAllValid() {
return [...this.validPermutations.keys()];
}
getValid(key) {
return this.validPermutations.get(key);
}
hasValid(key) {
return this.validPermutations.has(key);
}
}
class Config {
options;
arch;
bin;
binAliases;
binPath;
cacheDir;
channel;
configDir;
dataDir;
dirname;
flexibleTaxonomy;
home;
isSingleCommandCLI = false;
name;
npmRegistry;
nsisCustomization;
pjson;
platform;
plugins = new Map();
root;
shell;
theme;
topicSeparator = ':';
updateConfig;
userAgent;
userPJSON;
valid;
version;
warned = false;
windows;
_base = BASE;
_commandIDs;
_commands = new Map();
_topics = new Map();
commandPermutations = new Permutations();
pluginLoader;
rootPlugin;
topicPermutations = new Permutations();
constructor(options) {
this.options = options;
}
static async load(opts = module.filename || __dirname) {
(0, logger_1.setLogger)(opts);
// 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);
}
if (typeof opts === 'string')
opts = { root: opts };
if (isConfig(opts)) {
/**
* Reload the Config based on the version required by the command.
* This is needed because the command is given the Config instantiated
* by the root plugin, which may be a different version than the one
* required by the command.
*
* Doing this ensures that the command can freely use any method on Config that
* exists in the version of Config required by the command but may not exist on the
* root's instance of Config.
*/
if (BASE !== opts._base) {
debug(`reloading config from ${opts._base} to ${BASE}`);
const config = new Config({ ...opts.options, plugins: opts.plugins });
await config.load();
return config;
}
return opts;
}
const config = new Config(opts);
await config.load();
return config;
}
get commandIDs() {
if (this._commandIDs)
return this._commandIDs;
this._commandIDs = this.commands.map((c) => c.id);
return this._commandIDs;
}
get commands() {
return [...this._commands.values()];
}
get isProd() {
return (0, util_2.isProd)();
}
static get rootPlugin() {
return this.rootPlugin;
}
get topics() {
return [...this._topics.values()];
}
get versionDetails() {
const [cliVersion, architecture, nodeVersion] = this.userAgent.split(' ');
return {
architecture,
cliVersion,
nodeVersion,
osVersion: `${(0, node_os_1.type)()} ${(0, node_os_1.release)()}`,
pluginVersions: Object.fromEntries([...this.plugins.values()].map((p) => [p.name, { root: p.root, type: p.type, version: p.version }])),
rootPath: this.root,
shell: this.shell,
};
}
_shell() {
let shellPath;
const { COMSPEC } = process.env;
const SHELL = process.env.SHELL ?? (0, node_os_1.userInfo)().shell?.split(node_path_1.sep)?.pop();
if (SHELL) {
shellPath = SHELL.split('/');
}
else if (this.windows && process.title.toLowerCase().includes('powershell')) {
shellPath = ['powershell'];
}
else if (this.windows && process.title.toLowerCase().includes('command prompt')) {
shellPath = ['cmd.exe'];
}
else if (this.windows && COMSPEC) {
shellPath = COMSPEC.split(/\\|\//);
}
else {
shellPath = ['unknown'];
}
return shellPath.at(-1) ?? 'unknown';
}
dir(category) {
const base = process.env[`XDG_${category.toUpperCase()}_HOME`] ||
(this.windows && process.env.LOCALAPPDATA) ||
(0, node_path_1.join)(this.home, category === 'data' ? '.local/share' : '.' + category);
return (0, node_path_1.join)(base, this.dirname);
}
findCommand(id, opts = {}) {
const lookupId = this.getCmdLookupId(id);
const command = this._commands.get(lookupId);
if (opts.must && !command)
(0, errors_1.error)(`command ${lookupId} not found`);
return command;
}
/**
* Find all command ids that include the provided command id.
*
* For example, if the command ids are:
* - foo:bar:baz
* - one:two:three
*
* `bar` would return `foo:bar:baz`
*
* @param partialCmdId string
* @param argv string[] process.argv containing the flags and arguments provided by the user
* @returns string[]
*/
findMatches(partialCmdId, argv) {
const flags = argv
.filter((arg) => !(0, util_1.getHelpFlagAdditions)(this).includes(arg) && arg.startsWith('-'))
.map((a) => a.replaceAll('-', ''));
const possibleMatches = [...this.commandPermutations.get(partialCmdId)].map((k) => this._commands.get(k));
const matches = possibleMatches.filter((command) => {
const cmdFlags = Object.entries(command.flags).flatMap(([flag, def]) => def.char ? [def.char, flag] : [flag]);
// A command is a match if the provided flags belong to the full command
return flags.every((f) => cmdFlags.includes(f));
});
return matches;
}
findTopic(name, opts = {}) {
const lookupId = this.getTopicLookupId(name);
const topic = this._topics.get(lookupId);
if (topic)
return topic;
if (opts.must)
throw new Error(`topic ${name} not found`);
}
/**
* Returns an array of all command ids. If flexible taxonomy is enabled then all permutations will be appended to the array.
* @returns string[]
*/
getAllCommandIDs() {
return this.getAllCommands().map((c) => c.id);
}
/**
* Returns an array of all commands. If flexible taxonomy is enabled then all permutations will be appended to the array.
* @returns Command.Loadable[]
*/
getAllCommands() {
const commands = [...this._commands.values()];
const validPermutations = [...this.commandPermutations.getAllValid()];
for (const permutation of validPermutations) {
if (!this._commands.has(permutation)) {
const cmd = this._commands.get(this.getCmdLookupId(permutation));
commands.push({ ...cmd, id: permutation });
}
}
return commands;
}
getPluginsList() {
return [...this.plugins.values()];
}
// eslint-disable-next-line complexity
async load() {
settings_1.settings.performanceEnabled =
(settings_1.settings.performanceEnabled === undefined ? this.options.enablePerf : settings_1.settings.performanceEnabled) ?? false;
if (settings_1.settings.debug)
displayWarnings();
(0, logger_1.setLogger)(this.options);
const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, 'config.load');
this.pluginLoader = new plugin_loader_1.default({ plugins: this.options.plugins, root: this.options.root });
this.rootPlugin = await this.pluginLoader.loadRoot({ pjson: this.options.pjson });
// Cache the root plugin so that we can reference it later when determining if
// we should skip ts-node registration for an ESM plugin.
const cache = cache_1.default.getInstance();
cache.set('rootPlugin', this.rootPlugin);
cache.set('exitCodes', this.rootPlugin.pjson.oclif.exitCodes ?? {});
this.root = this.rootPlugin.root;
this.pjson = this.rootPlugin.pjson;
this.plugins.set(this.rootPlugin.name, this.rootPlugin);
this.root = this.rootPlugin.root;
this.pjson = this.rootPlugin.pjson;
this.name = this.pjson.name;
this.version = this.options.version || this.pjson.version || '0.0.0';
this.channel = this.options.channel || channelFromVersion(this.version);
this.valid = this.rootPlugin.valid;
this.arch = (0, node_os_1.arch)() === 'ia32' ? 'x86' : (0, node_os_1.arch)();
this.platform = is_wsl_1.default ? 'wsl' : (0, os_1.getPlatform)();
this.windows = this.platform === 'win32';
this.bin = this.pjson.oclif.bin || this.name;
this.binAliases = this.pjson.oclif.binAliases;
this.nsisCustomization = this.pjson.oclif.nsisCustomization;
this.dirname = this.pjson.oclif.dirname || this.name;
this.flexibleTaxonomy = this.pjson.oclif.flexibleTaxonomy || false;
// currently, only colons or spaces are valid separators
if (this.pjson.oclif.topicSeparator && [' ', ':'].includes(this.pjson.oclif.topicSeparator))
this.topicSeparator = this.pjson.oclif.topicSeparator;
if (this.platform === 'win32')
this.dirname = this.dirname.replace('/', '\\');
this.userAgent = `${this.name}/${this.version} ${this.platform}-${this.arch} node-${process.version}`;
this.shell = this._shell();
this.home = process.env.HOME || (this.windows && this.windowsHome()) || (0, os_1.getHomeDir)() || (0, node_os_1.tmpdir)();
this.cacheDir = this.scopedEnvVar('CACHE_DIR') || this.macosCacheDir() || this.dir('cache');
this.configDir = this.scopedEnvVar('CONFIG_DIR') || this.dir('config');
this.dataDir = this.scopedEnvVar('DATA_DIR') || this.dir('data');
this.binPath = this.scopedEnvVar('BINPATH');
this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.oclif.npmRegistry;
this.theme = await this.loadTheme();
this.updateConfig = {
...this.pjson.oclif.update,
node: this.pjson.oclif.update?.node ?? {},
s3: this.buildS3Config(),
};
this.isSingleCommandCLI = Boolean(typeof this.pjson.oclif.commands !== 'string' &&
this.pjson.oclif.commands?.strategy === 'single' &&
this.pjson.oclif.commands?.target);
this.maybeAdjustDebugSetting();
await this.loadPluginsAndCommands();
debug('config done');
marker?.addDetails({
commandPermutations: this.commands.length,
commands: [...this.plugins.values()].reduce((acc, p) => acc + p.commands.length, 0),
plugins: this.plugins.size,
topics: this.topics.length,
});
marker?.stop();
}
async loadPluginsAndCommands(opts) {
const pluginsMarker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, 'config.loadAllPlugins');
const { errors, plugins } = await this.pluginLoader.loadChildren({
dataDir: this.dataDir,
devPlugins: this.options.devPlugins,
force: opts?.force ?? false,
pluginAdditions: this.options.pluginAdditions,
rootPlugin: this.rootPlugin,
userPlugins: this.options.userPlugins,
});
this.plugins = plugins;
pluginsMarker?.stop();
const commandsMarker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, 'config.loadAllCommands');
for (const plugin of this.plugins.values()) {
this.loadCommands(plugin);
this.loadTopics(plugin);
}
commandsMarker?.stop();
for (const error of errors) {
this.warn(error);
}
}
async loadTheme() {
if (this.scopedEnvVarTrue('DISABLE_THEME'))
return;
const userThemeFile = (0, node_path_1.resolve)(this.configDir, 'theme.json');
const getDefaultTheme = async () => {
if (!this.pjson.oclif.theme)
return;
if (typeof this.pjson.oclif.theme === 'string') {
return (0, fs_1.safeReadJson)((0, node_path_1.resolve)(this.root, this.pjson.oclif.theme));
}
return this.pjson.oclif.theme;
};
const [defaultTheme, userTheme] = await Promise.all([
getDefaultTheme(),
(0, fs_1.safeReadJson)(userThemeFile),
]);
// Merge the default theme with the user theme, giving the user theme precedence.
const merged = { ...defaultTheme, ...userTheme };
return Object.keys(merged).length > 0 ? (0, theme_1.parseTheme)(merged) : undefined;
}
macosCacheDir() {
return (this.platform === 'darwin' && (0, node_path_1.join)(this.home, 'Library', 'Caches', this.dirname)) || undefined;
}
async runCommand(id, argv = [], cachedCommand = null) {
const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.runCommand#${id}`);
debug('runCommand %s %o', id, argv);
let c = cachedCommand ?? this.findCommand(id);
if (!c) {
const matches = this.flexibleTaxonomy ? this.findMatches(id, argv) : [];
const hookResult = this.flexibleTaxonomy && matches.length > 0
? await this.runHook('command_incomplete', { argv, id, matches })
: await this.runHook('command_not_found', { argv, id });
if (hookResult.successes[0])
return hookResult.successes[0].result;
if (hookResult.failures[0])
throw hookResult.failures[0].error;
throw new errors_1.CLIError(`command ${id} not found`);
}
if (this.isJitPluginCommand(c)) {
const pluginName = c.pluginName;
const pluginVersion = this.pjson.oclif.jitPlugins[pluginName];
const jitResult = await this.runHook('jit_plugin_not_installed', {
argv,
command: c,
id,
pluginName,
pluginVersion,
});
if (jitResult.failures[0])
throw jitResult.failures[0].error;
if (jitResult.successes[0]) {
await this.loadPluginsAndCommands({ force: true });
c = this.findCommand(id) ?? c;
}
else {
// this means that no jit_plugin_not_installed hook exists, so we should run the default behavior
const result = await this.runHook('command_not_found', { argv, id });
if (result.successes[0])
return result.successes[0].result;
if (result.failures[0])
throw result.failures[0].error;
throw new errors_1.CLIError(`command ${id} not found`);
}
}
const command = await c.load();
await this.runHook('prerun', { argv, Command: command });
const result = (await command.run(argv, this));
// If plugins:uninstall was run, we need to remove all the uninstalled plugins
// from this.plugins so that the postrun hook doesn't attempt to run any
// hooks that might have existed in the uninstalled plugins.
if (c.id === 'plugins:uninstall') {
for (const arg of argv)
this.plugins.delete(arg);
}
await this.runHook('postrun', { argv, Command: command, result });
marker?.addDetails({ command: id, plugin: c.pluginName });
marker?.stop();
return result;
}
async runHook(event, opts, timeout, captureErrors) {
const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.runHook#${event}`);
debug('start %s hook', event);
const search = (m) => {
if (typeof m === 'function')
return m;
if (m.default && typeof m.default === 'function')
return m.default;
return Object.values(m).find((m) => typeof m === 'function');
};
const withTimeout = async (ms, promise) => {
let id;
const timeout = new Promise((_, reject) => {
id = setTimeout(() => {
reject(new Error(`Timed out after ${ms} ms.`));
}, ms).unref();
});
return Promise.race([promise, timeout]).then((result) => {
clearTimeout(id);
return result;
});
};
const final = {
failures: [],
successes: [],
};
const plugins = ROOT_ONLY_HOOKS.has(event) ? [this.rootPlugin] : [...this.plugins.values()];
const promises = plugins.map(async (p) => {
const debug = (0, logger_1.makeDebug)([p.name, 'hooks', event].join(':'));
const context = {
config: this,
debug,
error(message, options = {}) {
(0, errors_1.error)(message, options);
},
exit(code = 0) {
(0, errors_1.exit)(code);
},
log(message, ...args) {
ux_1.ux.stdout(message, ...args);
},
warn(message) {
(0, errors_1.warn)(message);
},
};
const hooks = p.hooks[event] || [];
for (const hook of hooks) {
const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.runHook#${p.name}(${hook.target})`);
try {
/* eslint-disable no-await-in-loop */
const { filePath, isESM, module } = await (0, module_loader_1.loadWithData)(p, await (0, ts_path_1.tsPath)(p.root, hook.target, p));
debug('start', isESM ? '(import)' : '(require)', filePath);
// If no hook is found using the identifier, then we should `search` for the hook but only if the hook identifier is 'default'
// A named identifier (e.g. MY_HOOK) that isn't found indicates that the hook isn't implemented in the plugin.
const hookFn = module[hook.identifier] ?? (hook.identifier === 'default' ? search(module) : undefined);
if (!hookFn) {
debug('No hook found for hook definition:', hook);
continue;
}
const result = timeout
? await withTimeout(timeout, hookFn.call(context, { ...opts, config: this, context }))
: await hookFn.call(context, { ...opts, config: this, context });
final.successes.push({ plugin: p, result });
if (p.name === '@oclif/plugin-legacy' && event === 'init') {
this.insertLegacyPlugins(result);
}
debug('done');
}
catch (error) {
final.failures.push({ error: error, plugin: p });
debug(error);
// Do not throw the error if
// captureErrors is set to true
// error.oclif.exit is undefined or 0
// error.code is MODULE_NOT_FOUND
if (!captureErrors &&
error.oclif?.exit !== undefined &&
error.oclif?.exit !== 0 &&
error.code !== 'MODULE_NOT_FOUND')
throw error;
}
marker?.addDetails({
event,
hook: hook.target,
plugin: p.name,
});
marker?.stop();
}
});
await Promise.all(promises);
debug('%s hook done', event);
marker?.stop();
return final;
}
s3Key(type, ext, options = {}) {
if (typeof ext === 'object')
options = ext;
else if (ext)
options.ext = ext;
const template = this.updateConfig.s3?.templates?.[options.platform ? 'target' : 'vanilla'][type] ?? '';
return ejs.render(template, { ...this, ...options });
}
s3Url(key) {
const { host } = this.updateConfig.s3 ?? { host: undefined };
if (!host)
throw new Error('no s3 host is set');
const url = new node_url_1.URL(host);
url.pathname = (0, node_path_1.join)(url.pathname, key);
return url.toString();
}
scopedEnvVar(k) {
return process.env[this.scopedEnvVarKeys(k).find((k) => process.env[k])];
}
/**
* this DOES NOT account for bin aliases, use scopedEnvVarKeys instead which will account for bin aliases
* @param k {string}, the unscoped key you want to get the value for
* @returns {string} returns the env var key
*/
scopedEnvVarKey(k) {
return [this.bin, k]
.map((p) => p.replaceAll('@', '').replaceAll(/[/-]/g, '_'))
.join('_')
.toUpperCase();
}
/**
* gets the scoped env var keys for a given key, including bin aliases
* @param k {string}, the env key e.g. 'debug'
* @returns {string[]} e.g. ['SF_DEBUG', 'SFDX_DEBUG']
*/
scopedEnvVarKeys(k) {
return [this.bin, ...(this.binAliases ?? [])]
.filter(Boolean)
.map((alias) => [alias.replaceAll('@', '').replaceAll(/[/-]/g, '_'), k].join('_').toUpperCase());
}
scopedEnvVarTrue(k) {
const v = this.scopedEnvVar(k);
return v === '1' || v === 'true';
}
windowsHome() {
return this.windowsHomedriveHome() || this.windowsUserprofileHome();
}
windowsHomedriveHome() {
return process.env.HOMEDRIVE && process.env.HOMEPATH && (0, node_path_1.join)(process.env.HOMEDRIVE, process.env.HOMEPATH);
}
windowsUserprofileHome() {
return process.env.USERPROFILE;
}
buildS3Config() {
const s3 = this.pjson.oclif.update?.s3;
const bucket = this.scopedEnvVar('S3_BUCKET') ?? s3?.bucket;
const host = s3?.host ?? (bucket && `https://${bucket}.s3.amazonaws.com`);
const templates = {
...s3?.templates,
target: {
baseDir: '<%- bin %>',
manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- platform %>-<%- arch %>",
unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-<%- platform %>-<%- arch %><%- ext %>",
versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>",
...(s3?.templates && s3?.templates.target),
},
vanilla: {
baseDir: '<%- bin %>',
manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %>version",
unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %><%- ext %>",
versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %><%- ext %>",
...(s3?.templates && s3?.templates.vanilla),
},
};
return {
bucket,
host,
templates,
};
}
getCmdLookupId(id) {
if (this._commands.has(id))
return id;
if (this.commandPermutations.hasValid(id))
return this.commandPermutations.getValid(id);
return id;
}
getTopicLookupId(id) {
if (this._topics.has(id))
return id;
if (this.topicPermutations.hasValid(id))
return this.topicPermutations.getValid(id);
return id;
}
/**
* Insert legacy plugins
*
* Replace invalid CLI plugins (cli-engine plugins, mostly Heroku) loaded via `this.loadPlugins`
* with oclif-compatible ones returned by @oclif/plugin-legacy init hook.
*
* @param plugins array of oclif-compatible plugins
*/
insertLegacyPlugins(plugins) {
for (const plugin of plugins) {
this.plugins.set(plugin.name, plugin);
// Delete all commands from the legacy plugin so that we can re-add them.
// This is necessary because determinePriority will pick the initial
// command that was added, which won't have been converted by PluginLegacy yet.
for (const cmd of plugin.commands ?? []) {
this._commands.delete(cmd.id);
for (const alias of [...(cmd.aliases ?? []), ...(cmd.hiddenAliases ?? [])]) {
this._commands.delete(alias);
}
}
this.loadCommands(plugin);
}
}
isJitPluginCommand(c) {
// Return true if the command's plugin is listed under oclif.jitPlugins AND if the plugin hasn't been loaded to this.plugins
return (Object.keys(this.pjson.oclif.jitPlugins ?? {}).includes(c.pluginName ?? '') &&
Boolean(c?.pluginName && !this.plugins.has(c.pluginName)));
}
loadCommands(plugin) {
const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.loadCommands#${plugin.name}`, { plugin: plugin.name });
for (const command of plugin.commands) {
// set canonical command id
if (this._commands.has(command.id)) {
const prioritizedCommand = (0, determine_priority_1.determinePriority)(this.pjson.oclif.plugins ?? [], [
this._commands.get(command.id),
command,
]);
this._commands.set(prioritizedCommand.id, prioritizedCommand);
}
else {
this._commands.set(command.id, command);
}
// v3 moved command id permutations to the manifest, but some plugins may not have
// the new manifest yet. For those, we need to calculate the permutations here.
const permutations = this.flexibleTaxonomy && command.permutations === undefined
? (0, util_3.getCommandIdPermutations)(command.id)
: (command.permutations ?? [command.id]);
// set every permutation
for (const permutation of permutations) {
this.commandPermutations.add(permutation, command.id);
}
const handleAlias = (alias, hidden = false) => {
const aliasWithDefaultTopicSeparator = (0, ids_1.toStandardizedId)(alias, this);
if (this._commands.has(aliasWithDefaultTopicSeparator)) {
const prioritizedCommand = (0, determine_priority_1.determinePriority)(this.pjson.oclif.plugins ?? [], [
this._commands.get(aliasWithDefaultTopicSeparator),
command,
]);
this._commands.set(aliasWithDefaultTopicSeparator, {
...prioritizedCommand,
id: aliasWithDefaultTopicSeparator,
});
}
else {
this._commands.set(aliasWithDefaultTopicSeparator, { ...command, hidden, id: aliasWithDefaultTopicSeparator });
}
// set every permutation of the aliases
// v3 moved command alias permutations to the manifest, but some plugins may not have
// the new manifest yet. For those, we need to calculate the permutations here.
const aliasPermutations = this.flexibleTaxonomy && command.aliasPermutations === undefined
? (0, util_3.getCommandIdPermutations)(aliasWithDefaultTopicSeparator)
: (command.permutations ?? [aliasWithDefaultTopicSeparator]);
// set every permutation
for (const permutation of aliasPermutations) {
this.commandPermutations.add(permutation, command.id);
}
};
// set command aliases
for (const alias of command.aliases ?? []) {
handleAlias(alias);
}
// set hidden command aliases
for (const alias of command.hiddenAliases ?? []) {
handleAlias(alias, true);
}
}
marker?.addDetails({ commandCount: plugin.commands.length });
marker?.stop();
}
loadTopics(plugin) {
const marker = performance_1.Performance.mark(performance_1.OCLIF_MARKER_OWNER, `config.loadTopics#${plugin.name}`, { plugin: plugin.name });
for (const topic of (0, util_2.compact)(plugin.topics)) {
const existing = this._topics.get(topic.name);
if (existing) {
existing.description = topic.description || existing.description;
existing.hidden = existing.hidden || topic.hidden;
}
else {
this._topics.set(topic.name, topic);
}
const permutations = this.flexibleTaxonomy ? (0, util_3.getCommandIdPermutations)(topic.name) : [topic.name];
for (const permutation of permutations) {
this.topicPermutations.add(permutation, topic.name);
}
}
// Add missing topics for displaying help when partial commands are entered.
for (const c of plugin.commands.filter((c) => !c.hidden)) {
const parts = c.id.split(':');
while (parts.length > 0) {
const name = parts.join(':');
if (name && !this._topics.has(name)) {
this._topics.set(name, { description: c.summary || c.description, name });
}
parts.pop();
}
}
marker?.stop();
}
maybeAdjustDebugSetting() {
if (this.scopedEnvVarTrue('DEBUG')) {
settings_1.settings.debug = true;
displayWarnings();
}
}
warn(err, scope) {
if (this.warned)
return;
if (typeof err === 'string') {
process.emitWarning(err);
return;
}
if (err instanceof Error) {
const modifiedErr = err;
modifiedErr.name = `${err.name} Plugin: ${this.name}`;
modifiedErr.detail = (0, util_2.compact)([
err.detail,
`module: ${this._base}`,
scope && `task: ${scope}`,
`plugin: ${this.name}`,
`root: ${this.root}`,
'See more details with DEBUG=*',
]).join('\n');
process.emitWarning(err);
return;
}
// err is an object
process.emitWarning('Config.warn expected either a string or Error, but instead received an object');
err.name = `${err.name} Plugin: ${this.name}`;
err.detail = (0, util_2.compact)([
err.detail,
`module: ${this._base}`,
scope && `task: ${scope}`,
`plugin: ${this.name}`,
`root: ${this.root}`,
'See more details with DEBUG=*',
]).join('\n');
process.emitWarning(JSON.stringify(err));
}
}
exports.Config = Config;