UNPKG

6.99 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const cli_ux_1 = require("cli-ux");
4const fs = require("fs-extra");
5const loadJSON = require("load-json-file");
6const _ = require("lodash");
7const path = require("path");
8const readPkg = require("read-pkg");
9const util_1 = require("util");
10const Config = require(".");
11const ts_node_1 = require("./ts_node");
12const util_2 = require("./util");
13const debug = require('debug')('@anycli/config');
14const _pjson = require('../package.json');
15const loadedPlugins = {};
16class Plugin {
17 constructor(opts) {
18 this._base = `${_pjson.name}@${_pjson.version}`;
19 this.plugins = [];
20 this.type = opts.type || 'core';
21 const root = findRoot(opts.name, opts.root);
22 if (!root)
23 throw new Error(`could not find package.json with ${util_1.inspect(opts)}`);
24 if (loadedPlugins[root])
25 return loadedPlugins[root];
26 loadedPlugins[root] = this;
27 this.root = root;
28 debug('reading plugin %s', root);
29 this.pjson = readPkg.sync(path.join(root, 'package.json'));
30 this.name = this.pjson.name;
31 this.version = this.pjson.version;
32 if (!this.pjson.anycli) {
33 this.pjson.anycli = this.pjson['cli-engine'] || {};
34 }
35 this.valid = this.pjson.anycli.schema === 1;
36 this.topics = topicsToArray(this.pjson.anycli.topics || {});
37 this.hooks = _.mapValues(this.pjson.anycli.hooks || {}, _.castArray);
38 this.manifest = this._manifest();
39 this.loadPlugins();
40 }
41 get commandsDir() {
42 return ts_node_1.tsPath(this.root, this.pjson.anycli.commands);
43 }
44 allTopics() {
45 let topics = [...this.topics];
46 for (let plugin of this.plugins) {
47 topics = [...topics, ...plugin.allTopics()];
48 }
49 return topics;
50 }
51 allCommands() {
52 let commands = Object.entries(this.manifest.commands)
53 .map(([id, c]) => (Object.assign({}, c, { load: () => this._findCommand(id) })));
54 for (let plugin of this.plugins) {
55 commands = [...commands, ...plugin.allCommands()];
56 }
57 return commands;
58 }
59 findCommand(id, opts = {}) {
60 let command = this.manifest.commands[id];
61 if (command)
62 return Object.assign({}, command, { load: () => this._findCommand(id) });
63 for (let plugin of this.plugins) {
64 let command = plugin.findCommand(id);
65 if (command)
66 return command;
67 }
68 if (opts.must)
69 throw new Error(`command ${id} not found`);
70 }
71 _findCommand(id) {
72 const search = (cmd) => {
73 if (_.isFunction(cmd.run))
74 return cmd;
75 return Object.values(cmd).find((cmd) => _.isFunction(cmd.run));
76 };
77 const p = require.resolve(path.join(this.commandsDir, ...id.split(':')));
78 debug('require', p);
79 const cmd = search(require(p));
80 cmd.id = id;
81 return cmd;
82 }
83 findTopic(name, opts = {}) {
84 let topic = this.topics.find(t => t.name === name);
85 if (topic)
86 return topic;
87 for (let plugin of this.plugins) {
88 let topic = plugin.findTopic(name);
89 if (topic)
90 return topic;
91 }
92 if (opts.must)
93 throw new Error(`topic ${name} not found`);
94 }
95 async runHook(event, opts) {
96 const promises = (this.hooks[event] || [])
97 .map(async (hook) => {
98 try {
99 await util_2.undefault(require(ts_node_1.tsPath(this.root, hook)))(opts);
100 }
101 catch (err) {
102 if (err.code === 'EEXIT')
103 throw err;
104 cli_ux_1.default.warn(err);
105 }
106 });
107 promises.push(...this.plugins.map(p => p.runHook(event, opts)));
108 await Promise.all(promises);
109 }
110 // findCommand(id: string, opts?: {must: boolean}): ICommand | undefined
111 // findManifestCommand(id: string, opts: {must: true}): IManifestCommand
112 // findManifestCommand(id: string, opts?: {must: boolean}): IManifestCommand | undefined
113 // findTopic(id: string, opts: {must: true}): ITopic
114 // findTopic(id: string, opts?: {must: boolean}): ITopic | undefined
115 _manifest() {
116 try {
117 const manifest = loadJSON.sync(path.join(this.root, '.anycli.manifest.json'));
118 if (manifest.version !== this.version) {
119 cli_ux_1.default.warn(`Mismatched version in ${this.name} plugin manifest. Expected: ${this.version} Received: ${manifest.version}`);
120 }
121 else
122 return manifest;
123 }
124 catch (err) {
125 if (err.code !== 'ENOENT')
126 cli_ux_1.default.warn(err);
127 }
128 if (this.commandsDir)
129 return Config.Manifest.build(this.version, this.commandsDir, id => this._findCommand(id));
130 return { version: this.version, commands: {} };
131 }
132 loadPlugins(dev = false) {
133 const plugins = this.pjson.anycli[dev ? 'devPlugins' : 'plugins'];
134 if (!plugins || !plugins.length)
135 return;
136 debug(`loading ${dev ? 'dev ' : ''}plugins`, plugins);
137 for (let plugin of plugins || []) {
138 try {
139 let opts = { type: this.type, root: this.root };
140 if (typeof plugin === 'string')
141 opts.name = plugin;
142 else
143 opts = Object.assign({}, opts, plugin);
144 this.plugins.push(new Plugin(opts));
145 }
146 catch (err) {
147 cli_ux_1.default.warn(err);
148 }
149 }
150 return plugins;
151 }
152}
153exports.Plugin = Plugin;
154function topicsToArray(input, base) {
155 if (!input)
156 return [];
157 base = base ? `${base}:` : '';
158 if (Array.isArray(input)) {
159 return input.concat(_.flatMap(input, t => topicsToArray(t.subtopics, `${base}${t.name}`)));
160 }
161 return _.flatMap(Object.keys(input), k => {
162 return [Object.assign({}, input[k], { name: `${base}${k}` })].concat(topicsToArray(input[k].subtopics, `${base}${input[k].name}`));
163 });
164}
165/**
166 * find package root
167 * for packages installed into node_modules this will go up directories until
168 * it finds a node_modules directory with the plugin installed into it
169 *
170 * This is needed because of the deduping npm does
171 */
172function findRoot(name, root) {
173 // essentially just "cd .."
174 function* up(from) {
175 while (path.dirname(from) !== from) {
176 yield from;
177 from = path.dirname(from);
178 }
179 yield from;
180 }
181 for (let next of up(root)) {
182 let cur;
183 if (name) {
184 cur = path.join(next, 'node_modules', name, 'package.json');
185 }
186 else {
187 cur = path.join(next, 'package.json');
188 }
189 if (fs.pathExistsSync(cur))
190 return path.dirname(cur);
191 }
192}