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