UNPKG

@anycli/config

Version:

base config object and standard interfaces for anycli components

270 lines (269 loc) 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const errors_1 = require("@anycli/errors"); const os = require("os"); const path = require("path"); const util_1 = require("util"); const debug_1 = require("./debug"); const Plugin = require("./plugin"); const ts_node_1 = require("./ts_node"); const util_2 = require("./util"); const debug = debug_1.default(); const _pjson = require('../package.json'); class Config { constructor(options) { this.options = options; this._base = `${_pjson.name}@${_pjson.version}`; this.debug = 0; this.plugins = []; this.warned = false; } async load() { await this.loadPlugins(this.options.root, 'core', [{ root: this.options.root }], { must: true }); const plugin = this.plugins[0]; this.root = plugin.root; this.pjson = plugin.pjson; this.name = this.pjson.name; this.version = this.pjson.version; this.arch = (os.arch() === 'ia32' ? 'x86' : os.arch()); this.platform = os.platform(); this.windows = this.platform === 'win32'; this.bin = this.pjson.anycli.bin || this.name; this.dirname = this.pjson.anycli.dirname || this.name; this.userAgent = `${this.name}/${this.version} (${this.platform}-${this.arch}) node-${process.version}`; this.shell = this._shell(); this.debug = this._debug(); this.home = process.env.HOME || (this.windows && this.windowsHome()) || os.homedir() || os.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.errlog = path.join(this.cacheDir, 'error.log'); this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.anycli.npmRegistry || 'https://registry.yarnpkg.com'; await Promise.all([ this.loadCorePlugins(), this.loadUserPlugins(), this.loadDevPlugins(), ]); debug('config done'); } async loadCorePlugins() { if (this.pjson.anycli.plugins) { await this.loadPlugins(this.root, 'core', this.pjson.anycli.plugins); } } async loadDevPlugins() { if (this.options.devPlugins !== false) { try { const devPlugins = this.pjson.anycli.devPlugins; if (devPlugins) await this.loadPlugins(this.root, 'dev', devPlugins); } catch (err) { process.emitWarning(err); } } } async loadUserPlugins() { if (this.options.userPlugins !== false) { try { const userPJSONPath = path.join(this.dataDir, 'package.json'); const pjson = this.userPJSON = await util_2.loadJSON(userPJSONPath); if (!pjson.anycli) pjson.anycli = { schema: 1 }; await this.loadPlugins(userPJSONPath, 'user', pjson.anycli.plugins); } catch (err) { if (err.code !== 'ENOENT') process.emitWarning(err); } } } async runHook(event, opts) { debug('start %s hook', event); const context = { exit(code = 0) { errors_1.exit(code); }, log(message = '') { message = typeof message === 'string' ? message : util_1.inspect(message); process.stdout.write(message + '\n'); }, error(message, options = {}) { errors_1.error(message, options); }, warn(message) { errors_1.warn(message); }, }; const promises = this.plugins.map(p => { return Promise.all((p.hooks[event] || []) .map(async (hook) => { try { const f = ts_node_1.tsPath(p.root, hook); debug('hook', event, f); 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'); }; await search(require(f)).call(context, Object.assign({}, opts, { config: this })); } catch (err) { if (err && err.anycli && err.anycli.exit !== undefined) throw err; this.warn(err, `runHook ${event}`); } })); }); await Promise.all(promises); debug('done %s hook', event); } async runCommand(id, argv = []) { debug('runCommand %s %o', id, argv); const c = this.findCommand(id); if (!c) { await this.runHook('command_not_found', { id }); throw new errors_1.CLIError(`command ${id} not found`); } const command = c.load(); await this.runHook('prerun', { Command: command, argv }); await command.run(argv, this); } scopedEnvVar(k) { return process.env[this.scopedEnvVarKey(k)]; } scopedEnvVarTrue(k) { let v = process.env[this.scopedEnvVarKey(k)]; return v === '1' || v === 'true'; } scopedEnvVarKey(k) { return [this.bin, k] .map(p => p.replace(/@/g, '').replace(/[-\/]/g, '_')) .join('_') .toUpperCase(); } findCommand(id, opts = {}) { let command = this.commands.find(c => c.id === id || c.aliases.includes(id)); if (command) return command; if (opts.must) errors_1.error(`command ${id} not found`); } findTopic(name, opts = {}) { let topic = this.topics.find(t => t.name === name); if (topic) return topic; if (opts.must) throw new Error(`topic ${name} not found`); } get commands() { return util_2.flatMap(this.plugins, p => p.commands); } get commandIDs() { return util_2.uniq(this.commands.map(c => c.id)); } get topics() { let topics = []; for (let plugin of this.plugins) { for (let topic of util_2.compact(plugin.topics)) { let existing = topics.find(t => t.name === topic.name); if (existing) { existing.description = topic.description || existing.description; existing.hidden = existing.hidden || topic.hidden; } else topics.push(topic); } } // add missing topics for (let c of this.commands.filter(c => !c.hidden)) { let parts = c.id.split(':'); while (parts.length) { let name = parts.join(':'); if (name && !topics.find(t => t.name === name)) { topics.push({ name }); } parts.pop(); } } return topics; } dir(category) { const base = process.env[`XDG_${category.toUpperCase()}_HOME`] || (this.windows && process.env.LOCALAPPDATA) || path.join(this.home, category === 'data' ? '.local/share' : '.' + category); return path.join(base, this.dirname); } windowsHome() { return this.windowsHomedriveHome() || this.windowsUserprofileHome(); } windowsHomedriveHome() { return (process.env.HOMEDRIVE && process.env.HOMEPATH && path.join(process.env.HOMEDRIVE, process.env.HOMEPATH)); } windowsUserprofileHome() { return process.env.USERPROFILE; } macosCacheDir() { return this.platform === 'darwin' && path.join(this.home, 'Library', 'Caches', this.dirname) || undefined; } _shell() { let shellPath; const { SHELL, COMSPEC } = process.env; if (SHELL) { shellPath = SHELL.split('/'); } else if (this.windows && COMSPEC) { shellPath = COMSPEC.split(/\\|\//); } else { shellPath = ['unknown']; } return shellPath[shellPath.length - 1]; } _debug() { if (this.scopedEnvVarTrue('DEBUG')) return 1; try { const { enabled } = require('debug')(this.bin); if (enabled) return 1; } catch (_a) { } return 0; } async loadPlugins(root, type, plugins, options = {}) { if (!plugins.length) return; if (!plugins || !plugins.length) return; debug('loading plugins', plugins); await Promise.all((plugins || []).map(async (plugin) => { try { let opts = { type, root }; if (typeof plugin === 'string') { opts.name = plugin; } else { opts.name = plugin.name || opts.name; opts.tag = plugin.tag || opts.tag; opts.root = plugin.root || opts.root; } let instance = new Plugin.Plugin(opts); await instance.load(); this.plugins.push(instance); } catch (err) { if (options.must) throw err; this.warn(err, 'loadPlugins'); } })); } warn(err, scope) { if (this.warned) return; err.name = `${err.name} Plugin: ${this.name}`; err.detail = util_2.compact([err.detail, `module: ${this._base}`, scope && `task: ${scope}`, `plugin: ${this.name}`, `root: ${this.root}`]).join('\n'); process.emitWarning(err); } } exports.Config = Config; async function load(opts = (module.parent && module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) { if (typeof opts === 'string') opts = { root: opts }; if (isConfig(opts)) return opts; let config = new Config(opts); await config.load(); return config; } exports.load = load; function isConfig(o) { return o && !!o._base; }