UNPKG

8.61 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 ids = globby.sync(['**/*.+(js|ts)', '!**/*.+(d.ts|test.ts|test.js)'], { cwd: this.commandsDir })
72 .map(file => {
73 const p = path.parse(file);
74 const topics = p.dir.split('/');
75 let command = p.name !== 'index' && p.name;
76 return [...topics, command].filter(f => f).join(':');
77 });
78 this._debug('found commands', ids);
79 return ids;
80 }
81 findCommand(id, opts = {}) {
82 const fetch = () => {
83 if (!this.commandsDir)
84 return;
85 const search = (cmd) => {
86 if (typeof cmd.run === 'function')
87 return cmd;
88 if (cmd.default && cmd.default.run)
89 return cmd.default;
90 return Object.values(cmd).find((cmd) => typeof cmd.run === 'function');
91 };
92 const p = require.resolve(path.join(this.commandsDir, ...id.split(':')));
93 this._debug('require', p);
94 let m;
95 try {
96 m = require(p);
97 }
98 catch (err) {
99 if (!opts.must && err.code === 'MODULE_NOT_FOUND')
100 return;
101 throw err;
102 }
103 const cmd = search(m);
104 if (!cmd)
105 return;
106 cmd.id = id;
107 cmd.plugin = this;
108 return cmd;
109 };
110 const cmd = fetch();
111 if (!cmd && opts.must)
112 errors_1.error(`command ${id} not found`);
113 return cmd;
114 }
115 async _manifest(ignoreManifest, errorOnManifestCreate = false) {
116 const readManifest = async (dotfile = false) => {
117 try {
118 const p = path.join(this.root, `${dotfile ? '.' : ''}oclif.manifest.json`);
119 const manifest = await util_2.loadJSON(p);
120 if (!process.env.OCLIF_NEXT_VERSION && manifest.version.split('-')[0] !== this.version.split('-')[0]) {
121 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.`);
122 }
123 else {
124 this._debug('using manifest from', p);
125 return manifest;
126 }
127 }
128 catch (err) {
129 if (err.code === 'ENOENT') {
130 if (!dotfile)
131 return readManifest(true);
132 }
133 else {
134 this.warn(err, 'readManifest');
135 return;
136 }
137 }
138 };
139 if (!ignoreManifest) {
140 let manifest = await readManifest();
141 if (manifest)
142 return manifest;
143 }
144 return {
145 version: this.version,
146 commands: this.commandIDs.map(id => {
147 try {
148 return [id, command_1.Command.toCached(this.findCommand(id, { must: true }), this)];
149 }
150 catch (err) {
151 const scope = 'toCached';
152 if (!errorOnManifestCreate)
153 this.warn(err, scope);
154 else
155 throw this.addErrorScope(err, scope);
156 }
157 })
158 .filter((f) => !!f)
159 .reduce((commands, [id, c]) => {
160 commands[id] = c;
161 return commands;
162 }, {})
163 };
164 }
165 warn(err, scope) {
166 if (this.warned)
167 return;
168 if (typeof err === 'string')
169 err = new Error(err);
170 process.emitWarning(this.addErrorScope(err, scope));
171 }
172 addErrorScope(err, scope) {
173 err.name = `${err.name} Plugin: ${this.name}`;
174 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');
175 return err;
176 }
177}
178exports.Plugin = Plugin;
179function topicsToArray(input, base) {
180 if (!input)
181 return [];
182 base = base ? `${base}:` : '';
183 if (Array.isArray(input)) {
184 return input.concat(util_2.flatMap(input, t => topicsToArray(t.subtopics, `${base}${t.name}`)));
185 }
186 return util_2.flatMap(Object.keys(input), k => {
187 return [Object.assign({}, input[k], { name: `${base}${k}` })].concat(topicsToArray(input[k].subtopics, `${base}${input[k].name}`));
188 });
189}
190/**
191 * find package root
192 * for packages installed into node_modules this will go up directories until
193 * it finds a node_modules directory with the plugin installed into it
194 *
195 * This is needed because of the deduping npm does
196 */
197async function findRoot(name, root) {
198 // essentially just "cd .."
199 function* up(from) {
200 while (path.dirname(from) !== from) {
201 yield from;
202 from = path.dirname(from);
203 }
204 yield from;
205 }
206 for (let next of up(root)) {
207 let cur;
208 if (name) {
209 cur = path.join(next, 'node_modules', name, 'package.json');
210 if (await util_2.exists(cur))
211 return path.dirname(cur);
212 try {
213 let pkg = await util_2.loadJSON(path.join(next, 'package.json'));
214 if (pkg.name === name)
215 return next;
216 }
217 catch (_a) { }
218 }
219 else {
220 cur = path.join(next, 'package.json');
221 if (await util_2.exists(cur))
222 return path.dirname(cur);
223 }
224 }
225}