UNPKG

10.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const core_1 = require("@oclif/core");
4const cli_ux_1 = require("cli-ux");
5const fs = require("fs");
6const load_json_file_1 = require("load-json-file");
7const path = require("path");
8const semver = require("semver");
9const child_process_1 = require("child_process");
10const util_1 = require("./util");
11const yarn_1 = require("./yarn");
12const initPJSON = { private: true, oclif: { schema: 1, plugins: [] }, dependencies: {} };
13class Plugins {
14 constructor(config) {
15 this.config = config;
16 this.verbose = false;
17 this.yarn = new yarn_1.default({ config });
18 this.debug = require('debug')('@oclif/plugins');
19 }
20 async pjson() {
21 try {
22 const pjson = await (0, load_json_file_1.default)(this.pjsonPath);
23 return Object.assign(Object.assign(Object.assign({}, initPJSON), { dependencies: {} }), pjson);
24 }
25 catch (error) {
26 this.debug(error);
27 if (error.code !== 'ENOENT')
28 process.emitWarning(error);
29 return initPJSON;
30 }
31 }
32 async list() {
33 const pjson = await this.pjson();
34 return this.normalizePlugins(pjson.oclif.plugins);
35 }
36 async install(name, { tag = 'latest', force = false } = {}) {
37 try {
38 const yarnOpts = { cwd: this.config.dataDir, verbose: this.verbose };
39 await this.createPJSON();
40 let plugin;
41 const add = force ? ['add', '--force'] : ['add'];
42 const invalidPluginError = new core_1.Errors.CLIError('plugin is invalid', {
43 suggestions: [
44 'Plugin failed to install because it does not appear to be a valid CLI plugin.\nIf you are sure it is, contact the CLI developer noting this error.',
45 ],
46 });
47 if (name.includes(':')) {
48 // url
49 const url = name;
50 await this.yarn.exec([...add, url], yarnOpts);
51 name = Object.entries((await this.pjson()).dependencies || {}).find(([, u]) => u === url)[0];
52 plugin = await core_1.Config.load({ devPlugins: false, userPlugins: false, root: path.join(this.config.dataDir, 'node_modules', name), name });
53 await this.refresh(plugin.root);
54 if (!plugin.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) {
55 throw invalidPluginError;
56 }
57 await this.add({ name, url, type: 'user' });
58 }
59 else {
60 // npm
61 const range = semver.validRange(tag);
62 const unfriendly = this.unfriendlyName(name);
63 if (unfriendly && await this.npmHasPackage(unfriendly)) {
64 name = unfriendly;
65 }
66 await this.yarn.exec([...add, `${name}@${tag}`], yarnOpts);
67 plugin = await core_1.Config.load({ devPlugins: false, userPlugins: false, root: path.join(this.config.dataDir, 'node_modules', name), name });
68 if (!plugin.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) {
69 throw invalidPluginError;
70 }
71 await this.refresh(plugin.root);
72 await this.add({ name, tag: range || tag, type: 'user' });
73 }
74 return plugin;
75 }
76 catch (error) {
77 await this.uninstall(name).catch(error => this.debug(error));
78 if (String(error).includes('EACCES')) {
79 throw new core_1.Errors.CLIError(error, {
80 suggestions: [
81 `Plugin failed to install because of a permissions error.\nDoes your current user own the directory ${this.config.dataDir}?`,
82 ],
83 });
84 }
85 throw error;
86 }
87 }
88 // if yarn.lock exists, fetch locked dependencies
89 async refresh(root, { prod = true } = {}) {
90 if (fs.existsSync(path.join(root, 'yarn.lock'))) {
91 // use yarn.lock to fetch dependencies
92 await this.yarn.exec(prod ? ['--prod'] : [], { cwd: root, verbose: this.verbose });
93 }
94 }
95 async link(p) {
96 const c = await core_1.Config.load(path.resolve(p));
97 cli_ux_1.default.action.start(`${this.config.name}: linking plugin ${c.name}`);
98 if (!c.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) {
99 throw new core_1.Errors.CLIError('plugin is not a valid oclif plugin');
100 }
101 await this.refresh(c.root, { prod: false });
102 await this.add({ type: 'link', name: c.name, root: c.root });
103 }
104 async add(plugin) {
105 const pjson = await this.pjson();
106 pjson.oclif.plugins = (0, util_1.uniq)([...pjson.oclif.plugins || [], plugin]);
107 await this.savePJSON(pjson);
108 }
109 async remove(name) {
110 const pjson = await this.pjson();
111 if (pjson.dependencies)
112 delete pjson.dependencies[name];
113 pjson.oclif.plugins = this.normalizePlugins(pjson.oclif.plugins)
114 .filter(p => p.name !== name);
115 await this.savePJSON(pjson);
116 }
117 async uninstall(name) {
118 try {
119 const pjson = await this.pjson();
120 if ((pjson.oclif.plugins || []).find(p => typeof p === 'object' && p.type === 'user' && p.name === name)) {
121 await this.yarn.exec(['remove', name], { cwd: this.config.dataDir, verbose: this.verbose });
122 }
123 }
124 catch (error) {
125 cli_ux_1.default.warn(error);
126 }
127 finally {
128 await this.remove(name);
129 }
130 }
131 // In this case we want these operations to happen
132 // sequentially so the `no-await-in-loop` rule is ugnored
133 /* eslint-disable no-await-in-loop */
134 async update() {
135 let plugins = (await this.list()).filter((p) => p.type === 'user');
136 if (plugins.length === 0)
137 return;
138 cli_ux_1.default.action.start(`${this.config.name}: Updating plugins`);
139 // migrate deprecated plugins
140 const aliases = this.config.pjson.oclif.aliases || {};
141 for (const [name, to] of Object.entries(aliases)) {
142 const plugin = plugins.find(p => p.name === name);
143 if (!plugin)
144 continue;
145 if (to)
146 await this.install(to);
147 await this.uninstall(name);
148 plugins = plugins.filter(p => p.name !== name);
149 }
150 if (plugins.find(p => Boolean(p.url))) {
151 await this.yarn.exec(['upgrade'], { cwd: this.config.dataDir, verbose: this.verbose });
152 }
153 const npmPlugins = plugins.filter(p => !p.url);
154 if (npmPlugins.length > 0) {
155 await this.yarn.exec(['add', ...npmPlugins.map(p => `${p.name}@${p.tag}`)], { cwd: this.config.dataDir, verbose: this.verbose });
156 }
157 for (const p of plugins) {
158 await this.refresh(path.join(this.config.dataDir, 'node_modules', p.name));
159 }
160 cli_ux_1.default.action.stop();
161 }
162 /* eslint-enable no-await-in-loop */
163 async hasPlugin(name) {
164 var _a, _b;
165 const list = await this.list();
166 const friendly = list.find(p => this.friendlyName(p.name) === this.friendlyName(name));
167 const unfriendly = list.find(p => this.unfriendlyName(p.name) === this.unfriendlyName(name));
168 const link = list.find(p => p.type === 'link' && path.resolve(p.root) === path.resolve(name));
169 return ((_b = (_a = friendly !== null && friendly !== void 0 ? friendly : unfriendly) !== null && _a !== void 0 ? _a : link) !== null && _b !== void 0 ? _b : false);
170 }
171 async yarnNodeVersion() {
172 try {
173 const f = await (0, load_json_file_1.default)(path.join(this.config.dataDir, 'node_modules', '.yarn-integrity'));
174 return f.nodeVersion;
175 }
176 catch (error) {
177 if (error.code !== 'ENOENT')
178 cli_ux_1.default.warn(error);
179 }
180 }
181 unfriendlyName(name) {
182 if (name.includes('@'))
183 return;
184 const scope = this.config.pjson.oclif.scope;
185 if (!scope)
186 return;
187 return `@${scope}/plugin-${name}`;
188 }
189 async maybeUnfriendlyName(name) {
190 const unfriendly = this.unfriendlyName(name);
191 this.debug(`checking registry for expanded package name ${unfriendly}`);
192 if (unfriendly && await this.npmHasPackage(unfriendly)) {
193 return unfriendly;
194 }
195 this.debug(`expanded package name ${unfriendly} not found, using given package name ${name}`);
196 return name;
197 }
198 friendlyName(name) {
199 const scope = this.config.pjson.oclif.scope;
200 if (!scope)
201 return name;
202 const match = name.match(`@${scope}/plugin-(.+)`);
203 if (!match)
204 return name;
205 return match[1];
206 }
207 // private async loadPlugin(plugin: Config.PJSON.PluginTypes) {
208 // return Config.load({...plugin as any, root: this.config.dataDir})
209 // }
210 async createPJSON() {
211 if (!fs.existsSync(this.pjsonPath)) {
212 await this.savePJSON(initPJSON);
213 }
214 }
215 get pjsonPath() {
216 return path.join(this.config.dataDir, 'package.json');
217 }
218 get npmRegistry() {
219 return this.config.npmRegistry || 'https://registry.npmjs.org';
220 }
221 async npmHasPackage(name) {
222 return new Promise((resolve, reject) => {
223 (0, child_process_1.exec)(`npm show ${name} dist-tags`, {
224 encoding: 'utf-8',
225 maxBuffer: 2048 * 2048,
226 }, error => {
227 if (error) {
228 try {
229 return resolve(false);
230 }
231 catch (_a) {
232 reject(new Error(`Could not run npm show for ${name}`));
233 }
234 }
235 else {
236 return resolve(true);
237 }
238 });
239 });
240 }
241 async savePJSON(pjson) {
242 pjson.oclif.plugins = this.normalizePlugins(pjson.oclif.plugins);
243 const fs = require('fs-extra');
244 await fs.outputJSON(this.pjsonPath, pjson, { spaces: 2 });
245 }
246 normalizePlugins(input) {
247 let plugins = (input || []).map(p => {
248 if (typeof p === 'string') {
249 return { name: p, type: 'user', tag: 'latest' };
250 }
251 return p;
252 });
253 plugins = (0, util_1.uniqWith)(plugins, (a, b) => a.name === b.name || (a.type === 'link' && b.type === 'link' && a.root === b.root));
254 return plugins;
255 }
256}
257exports.default = Plugins;