UNPKG

8.72 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 _pjson = require('../package.json');
11class Plugin {
12 constructor(options) {
13 this.options = options;
14 // static loadedPlugins: {[name: string]: Plugin} = {}
15 this._base = `${_pjson.name}@${_pjson.version}`;
16 this.valid = false;
17 this.alreadyLoaded = false;
18 this._debug = debug_1.default();
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 this._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 const pjsonPath = path.join(root, 'package.json');
32 if (!this.name)
33 throw new Error(`no name in ${pjsonPath}`);
34 if (!this.pjson.files)
35 this.warn(`files attribute must be specified in ${pjsonPath}`);
36 this._debug = debug_1.default(this.name);
37 this.version = this.pjson.version;
38 if (this.pjson.oclif) {
39 this.valid = true;
40 }
41 else {
42 this.pjson.oclif = this.pjson['cli-engine'] || {};
43 }
44 this.hooks = util_2.mapValues(this.pjson.oclif.hooks || {}, i => Array.isArray(i) ? i : [i]);
45 this.manifest = await this._manifest(!!this.options.ignoreManifest, !!this.options.errorOnManifestCreate);
46 this.commands = Object.entries(this.manifest.commands)
47 .map(([id, c]) => (Object.assign({}, c, { load: () => this.findCommand(id, { must: true }) })));
48 this.commands.sort((a, b) => {
49 if (a.id < b.id)
50 return -1;
51 if (a.id > b.id)
52 return 1;
53 return 0;
54 });
55 }
56 get topics() { return topicsToArray(this.pjson.oclif.topics || {}); }
57 get commandsDir() { return ts_node_1.tsPath(this.root, this.pjson.oclif.commands); }
58 get commandIDs() {
59 if (!this.commandsDir)
60 return [];
61 let globby;
62 try {
63 const globbyPath = require.resolve('globby', { paths: [this.root, __dirname] });
64 globby = require(globbyPath);
65 }
66 catch (err) {
67 this.warn(err, 'not loading commands, globby not found');
68 return [];
69 }
70 this._debug(`loading IDs from ${this.commandsDir}`);
71 const patterns = [
72 '**/*.+(js|ts)',
73 '!**/*.+(d.ts|test.ts|test.js|spec.ts|spec.js)',
74 ];
75 const ids = globby.sync(patterns, { cwd: this.commandsDir })
76 .map(file => {
77 const p = path.parse(file);
78 const topics = p.dir.split('/');
79 let command = p.name !== 'index' && p.name;
80 return [...topics, command].filter(f => f).join(':');
81 });
82 this._debug('found commands', ids);
83 return ids;
84 }
85 findCommand(id, opts = {}) {
86 const fetch = () => {
87 if (!this.commandsDir)
88 return;
89 const search = (cmd) => {
90 if (typeof cmd.run === 'function')
91 return cmd;
92 if (cmd.default && cmd.default.run)
93 return cmd.default;
94 return Object.values(cmd).find((cmd) => typeof cmd.run === 'function');
95 };
96 const p = require.resolve(path.join(this.commandsDir, ...id.split(':')));
97 this._debug('require', p);
98 let m;
99 try {
100 m = require(p);
101 }
102 catch (err) {
103 if (!opts.must && err.code === 'MODULE_NOT_FOUND')
104 return;
105 throw err;
106 }
107 const cmd = search(m);
108 if (!cmd)
109 return;
110 cmd.id = id;
111 cmd.plugin = this;
112 return cmd;
113 };
114 const cmd = fetch();
115 if (!cmd && opts.must)
116 errors_1.error(`command ${id} not found`);
117 return cmd;
118 }
119 async _manifest(ignoreManifest, errorOnManifestCreate = false) {
120 const readManifest = async (dotfile = false) => {
121 try {
122 const p = path.join(this.root, `${dotfile ? '.' : ''}oclif.manifest.json`);
123 const manifest = await util_2.loadJSON(p);
124 if (!process.env.OCLIF_NEXT_VERSION && manifest.version.split('-')[0] !== this.version.split('-')[0]) {
125 process.emitWarning(`Mismatched version in ${this.name} plugin manifest. Expected: ${this.version} Received: ${manifest.version}\nThis usually means you have an oclif.manifest.json file that should be deleted in development. This file should be automatically generated when publishing.`);
126 }
127 else {
128 this._debug('using manifest from', p);
129 return manifest;
130 }
131 }
132 catch (err) {
133 if (err.code === 'ENOENT') {
134 if (!dotfile)
135 return readManifest(true);
136 }
137 else {
138 this.warn(err, 'readManifest');
139 return;
140 }
141 }
142 };
143 if (!ignoreManifest) {
144 let manifest = await readManifest();
145 if (manifest)
146 return manifest;
147 }
148 return {
149 version: this.version,
150 commands: this.commandIDs.map(id => {
151 try {
152 return [id, command_1.Command.toCached(this.findCommand(id, { must: true }), this)];
153 }
154 catch (err) {
155 const scope = 'toCached';
156 if (!errorOnManifestCreate)
157 this.warn(err, scope);
158 else
159 throw this.addErrorScope(err, scope);
160 }
161 })
162 .filter((f) => !!f)
163 .reduce((commands, [id, c]) => {
164 commands[id] = c;
165 return commands;
166 }, {})
167 };
168 }
169 warn(err, scope) {
170 if (this.warned)
171 return;
172 if (typeof err === 'string')
173 err = new Error(err);
174 process.emitWarning(this.addErrorScope(err, scope));
175 }
176 addErrorScope(err, scope) {
177 err.name = `${err.name} Plugin: ${this.name}`;
178 err.detail = util_2.compact([err.detail, `module: ${this._base}`, scope && `task: ${scope}`, `plugin: ${this.name}`, `root: ${this.root}`, 'See more details with DEBUG=*']).join('\n');
179 return err;
180 }
181}
182exports.Plugin = Plugin;
183function topicsToArray(input, base) {
184 if (!input)
185 return [];
186 base = base ? `${base}:` : '';
187 if (Array.isArray(input)) {
188 return input.concat(util_2.flatMap(input, t => topicsToArray(t.subtopics, `${base}${t.name}`)));
189 }
190 return util_2.flatMap(Object.keys(input), k => {
191 input[k].name = k;
192 return [Object.assign({}, input[k], { name: `${base}${k}` })].concat(topicsToArray(input[k].subtopics, `${base}${input[k].name}`));
193 });
194}
195/**
196 * find package root
197 * for packages installed into node_modules this will go up directories until
198 * it finds a node_modules directory with the plugin installed into it
199 *
200 * This is needed because of the deduping npm does
201 */
202async function findRoot(name, root) {
203 // essentially just "cd .."
204 function* up(from) {
205 while (path.dirname(from) !== from) {
206 yield from;
207 from = path.dirname(from);
208 }
209 yield from;
210 }
211 for (let next of up(root)) {
212 let cur;
213 if (name) {
214 cur = path.join(next, 'node_modules', name, 'package.json');
215 if (await util_2.exists(cur))
216 return path.dirname(cur);
217 try {
218 let pkg = await util_2.loadJSON(path.join(next, 'package.json'));
219 if (pkg.name === name)
220 return next;
221 }
222 catch (_a) { }
223 }
224 else {
225 cur = path.join(next, 'package.json');
226 if (await util_2.exists(cur))
227 return path.dirname(cur);
228 }
229 }
230}