UNPKG

7.51 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const errors_1 = require("@oclif/errors");
4const path = require("path");
5const util_1 = require("util");
6const command_1 = require("./command");
7const debug_1 = require("./debug");
8const ts_node_1 = require("./ts_node");
9const util_2 = require("./util");
10const debug = debug_1.default();
11const _pjson = require('../package.json');
12class Plugin {
13 constructor(options) {
14 this.options = options;
15 // static loadedPlugins: {[name: string]: Plugin} = {}
16 this._base = `${_pjson.name}@${_pjson.version}`;
17 this.valid = false;
18 this.alreadyLoaded = false;
19 this.warned = false;
20 }
21 async load() {
22 this.type = this.options.type || 'core';
23 this.tag = this.options.tag;
24 const root = await findRoot(this.options.name, this.options.root);
25 if (!root)
26 throw new Error(`could not find package.json with ${util_1.inspect(this.options)}`);
27 this.root = root;
28 debug('reading %s plugin %s', this.type, root);
29 this.pjson = await util_2.loadJSON(path.join(root, 'package.json'));
30 this.name = this.pjson.name;
31 this.version = this.pjson.version;
32 if (this.pjson.oclif) {
33 this.valid = true;
34 }
35 else {
36 this.pjson.oclif = this.pjson['cli-engine'] || {};
37 }
38 this.hooks = util_2.mapValues(this.pjson.oclif.hooks || {}, i => Array.isArray(i) ? i : [i]);
39 this.manifest = await this._manifest(!!this.options.ignoreManifest, !!this.options.errorOnManifestCreate);
40 this.commands = Object.entries(this.manifest.commands)
41 .map(([id, c]) => (Object.assign({}, c, { load: () => this.findCommand(id, { must: true }) })));
42 }
43 get topics() { return topicsToArray(this.pjson.oclif.topics || {}); }
44 get commandsDir() { return ts_node_1.tsPath(this.root, this.pjson.oclif.commands); }
45 get commandIDs() {
46 if (!this.commandsDir)
47 return [];
48 let globby;
49 try {
50 globby = require('globby');
51 }
52 catch (_a) {
53 debug('not loading plugins, globby not found');
54 return [];
55 }
56 debug(`loading IDs from ${this.commandsDir}`);
57 const ids = globby.sync(['**/*.+(js|ts)', '!**/*.+(d.ts|test.ts|test.js)'], { cwd: this.commandsDir })
58 .map(file => {
59 const p = path.parse(file);
60 const topics = p.dir.split('/');
61 let command = p.name !== 'index' && p.name;
62 return [...topics, command].filter(f => f).join(':');
63 });
64 debug('found ids', ids);
65 return ids;
66 }
67 findCommand(id, opts = {}) {
68 const fetch = () => {
69 if (!this.commandsDir)
70 return;
71 const search = (cmd) => {
72 if (typeof cmd.run === 'function')
73 return cmd;
74 if (cmd.default && cmd.default.run)
75 return cmd.default;
76 return Object.values(cmd).find((cmd) => typeof cmd.run === 'function');
77 };
78 const p = require.resolve(path.join(this.commandsDir, ...id.split(':')));
79 debug('require', p);
80 let m;
81 try {
82 m = require(p);
83 }
84 catch (err) {
85 if (!opts.must && err.code === 'MODULE_NOT_FOUND')
86 return;
87 throw err;
88 }
89 const cmd = search(m);
90 if (!cmd)
91 return;
92 cmd.id = id;
93 cmd.plugin = this;
94 return cmd;
95 };
96 const cmd = fetch();
97 if (!cmd && opts.must)
98 errors_1.error(`command ${id} not found`);
99 return cmd;
100 }
101 async _manifest(ignoreManifest, errorOnManifestCreate = false) {
102 const readManifest = async () => {
103 try {
104 const p = path.join(this.root, '.oclif.manifest.json');
105 const manifest = await util_2.loadJSON(p);
106 if (!process.env.OCLIF_NEXT_VERSION && manifest.version.split('-')[0] !== this.version.split('-')[0]) {
107 process.emitWarning(`Mismatched version in ${this.name} plugin manifest. Expected: ${this.version} Received: ${manifest.version}`);
108 }
109 else {
110 debug('using manifest from', p);
111 return manifest;
112 }
113 }
114 catch (err) {
115 if (err.code !== 'ENOENT')
116 this.warn(err, 'readManifest');
117 }
118 };
119 if (!ignoreManifest) {
120 let manifest = await readManifest();
121 if (manifest)
122 return manifest;
123 }
124 return {
125 version: this.version,
126 commands: this.commandIDs.map(id => {
127 try {
128 return [id, command_1.Command.toCached(this.findCommand(id, { must: true }), this)];
129 }
130 catch (err) {
131 const scope = 'toCached';
132 if (!errorOnManifestCreate)
133 this.warn(err, scope);
134 else
135 throw this.addErrorScope(err, scope);
136 }
137 })
138 .filter((f) => !!f)
139 .reduce((commands, [id, c]) => {
140 commands[id] = c;
141 return commands;
142 }, {})
143 };
144 }
145 warn(err, scope) {
146 if (this.warned)
147 return;
148 process.emitWarning(this.addErrorScope(err, scope));
149 }
150 addErrorScope(err, scope) {
151 err.name = `${err.name} Plugin: ${this.name}`;
152 err.detail = util_2.compact([err.detail, `module: ${this._base}`, scope && `task: ${scope}`, `plugin: ${this.name}`, `root: ${this.root}`]).join('\n');
153 return err;
154 }
155}
156exports.Plugin = Plugin;
157function topicsToArray(input, base) {
158 if (!input)
159 return [];
160 base = base ? `${base}:` : '';
161 if (Array.isArray(input)) {
162 return input.concat(util_2.flatMap(input, t => topicsToArray(t.subtopics, `${base}${t.name}`)));
163 }
164 return util_2.flatMap(Object.keys(input), k => {
165 return [Object.assign({}, input[k], { name: `${base}${k}` })].concat(topicsToArray(input[k].subtopics, `${base}${input[k].name}`));
166 });
167}
168/**
169 * find package root
170 * for packages installed into node_modules this will go up directories until
171 * it finds a node_modules directory with the plugin installed into it
172 *
173 * This is needed because of the deduping npm does
174 */
175async function findRoot(name, root) {
176 // essentially just "cd .."
177 function* up(from) {
178 while (path.dirname(from) !== from) {
179 yield from;
180 from = path.dirname(from);
181 }
182 yield from;
183 }
184 for (let next of up(root)) {
185 let cur;
186 if (name) {
187 cur = path.join(next, 'node_modules', name, 'package.json');
188 if (await util_2.exists(cur))
189 return path.dirname(cur);
190 try {
191 let pkg = await util_2.loadJSON(path.join(next, 'package.json'));
192 if (pkg.name === name)
193 return next;
194 }
195 catch (_a) { }
196 }
197 else {
198 cur = path.join(next, 'package.json');
199 if (await util_2.exists(cur))
200 return path.dirname(cur);
201 }
202 }
203}