@anycli/config
Version:
base config object and standard interfaces for anycli components
197 lines (196 loc) • 7.16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("@anycli/errors");
const path = require("path");
const util_1 = require("util");
const command_1 = require("./command");
const debug_1 = require("./debug");
const ts_node_1 = require("./ts_node");
const util_2 = require("./util");
const debug = debug_1.default();
const _pjson = require('../package.json');
class Plugin {
constructor(options) {
this.options = options;
// static loadedPlugins: {[name: string]: Plugin} = {}
this._base = `${_pjson.name}@${_pjson.version}`;
this.valid = false;
this.alreadyLoaded = false;
this.warned = false;
}
async load() {
this.type = this.options.type || 'core';
this.tag = this.options.tag;
const root = await findRoot(this.options.name, this.options.root);
if (!root)
throw new Error(`could not find package.json with ${util_1.inspect(this.options)}`);
this.root = root;
debug('reading %s plugin %s', this.type, root);
this.pjson = await util_2.loadJSON(path.join(root, 'package.json'));
this.name = this.pjson.name;
this.version = this.pjson.version;
if (this.pjson.anycli) {
this.valid = true;
}
else {
this.pjson.anycli = this.pjson['cli-engine'] || {};
}
this.hooks = util_2.mapValues(this.pjson.anycli.hooks || {}, i => Array.isArray(i) ? i : [i]);
this.manifest = await this._manifest(!!this.options.ignoreManifest);
this.commands = Object.entries(this.manifest.commands)
.map(([id, c]) => (Object.assign({}, c, { load: () => this.findCommand(id, { must: true }) })));
}
get topics() { return topicsToArray(this.pjson.anycli.topics || {}); }
get commandsDir() { return ts_node_1.tsPath(this.root, this.pjson.anycli.commands); }
get commandIDs() {
if (!this.commandsDir)
return [];
let globby;
try {
globby = require('globby');
}
catch (_a) {
debug('not loading plugins, globby not found');
return [];
}
debug(`loading IDs from ${this.commandsDir}`);
const ids = globby.sync(['**/*.+(js|ts)', '!**/*.+(d.ts|test.ts|test.js)'], { cwd: this.commandsDir })
.map(file => {
const p = path.parse(file);
const topics = p.dir.split('/');
let command = p.name !== 'index' && p.name;
return [...topics, command].filter(f => f).join(':');
});
debug('found ids', ids);
return ids;
}
findCommand(id, opts = {}) {
const fetch = () => {
if (!this.commandsDir)
return;
const search = (cmd) => {
if (typeof cmd.run === 'function')
return cmd;
if (cmd.default && cmd.default.run)
return cmd.default;
return Object.values(cmd).find((cmd) => typeof cmd.run === 'function');
};
const p = require.resolve(path.join(this.commandsDir, ...id.split(':')));
debug('require', p);
let m;
try {
m = require(p);
}
catch (err) {
if (!opts.must && err.code === 'MODULE_NOT_FOUND')
return;
throw err;
}
const cmd = search(m);
if (!cmd)
return;
cmd.id = id;
cmd.plugin = this;
return cmd;
};
const cmd = fetch();
if (!cmd && opts.must)
errors_1.error(`command ${id} not found`);
return cmd;
}
async _manifest(ignoreManifest) {
const readManifest = async () => {
try {
const p = path.join(this.root, '.anycli.manifest.json');
const manifest = await util_2.loadJSON(p);
if (!process.env.ANYCLI_NEXT_VERSION && manifest.version !== this.version) {
process.emitWarning(`Mismatched version in ${this.name} plugin manifest. Expected: ${this.version} Received: ${manifest.version}`);
}
else {
debug('using manifest from', p);
return manifest;
}
}
catch (err) {
if (err.code !== 'ENOENT')
this.warn(err, 'readManifest');
}
};
if (!ignoreManifest) {
let manifest = await readManifest();
if (manifest)
return manifest;
}
return {
version: this.version,
commands: this.commandIDs.map(id => {
try {
return [id, command_1.Command.toCached(this.findCommand(id, { must: true }), this)];
}
catch (err) {
this.warn(err, 'toCached');
}
})
.filter((f) => !!f)
.reduce((commands, [id, c]) => {
commands[id] = c;
return commands;
}, {})
};
}
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.Plugin = Plugin;
function topicsToArray(input, base) {
if (!input)
return [];
base = base ? `${base}:` : '';
if (Array.isArray(input)) {
return input.concat(util_2.flatMap(input, t => topicsToArray(t.subtopics, `${base}${t.name}`)));
}
return util_2.flatMap(Object.keys(input), k => {
return [Object.assign({}, input[k], { name: `${base}${k}` })].concat(topicsToArray(input[k].subtopics, `${base}${input[k].name}`));
});
}
/**
* find package root
* for packages installed into node_modules this will go up directories until
* it finds a node_modules directory with the plugin installed into it
*
* This is needed because of the deduping npm does
*/
async function findRoot(name, root) {
// essentially just "cd .."
function* up(from) {
while (path.dirname(from) !== from) {
yield from;
from = path.dirname(from);
}
yield from;
}
for (let next of up(root)) {
let cur;
if (name) {
cur = path.join(next, 'node_modules', name, 'package.json');
if (await util_2.exists(cur))
return path.dirname(cur);
try {
let pkg = await util_2.loadJSON(path.join(next, 'package.json'));
if (pkg.name === name)
return next;
}
catch (_a) { }
}
else {
cur = path.join(next, 'package.json');
if (await util_2.exists(cur))
return path.dirname(cur);
}
}
}