UNPKG

9.57 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const Config = require("@oclif/config");
4const errors_1 = require("@oclif/errors");
5const cli_ux_1 = require("cli-ux");
6const fs = require("fs");
7const load_json_file_1 = require("load-json-file");
8const path = require("path");
9const semver = require("semver");
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 load_json_file_1.default(this.pjsonPath);
23 return Object.assign(Object.assign(Object.assign({}, initPJSON), { oclif: Object.assign(Object.assign({}, initPJSON.oclif), pjson.oclif), 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 if (name.includes(':')) {
43 // url
44 const url = name;
45 await this.yarn.exec([...add, url], yarnOpts);
46 name = Object.entries((await this.pjson()).dependencies || {}).find(([, u]) => u === url)[0];
47 plugin = await Config.load({ devPlugins: false, userPlugins: false, root: path.join(this.config.dataDir, 'node_modules', name), name });
48 await this.refresh(plugin.root);
49 if (!plugin.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) {
50 throw new Error('plugin is invalid');
51 }
52 await this.add({ name, url, type: 'user' });
53 }
54 else {
55 // npm
56 const range = semver.validRange(tag);
57 const unfriendly = this.unfriendlyName(name);
58 if (unfriendly && await this.npmHasPackage(unfriendly)) {
59 name = unfriendly;
60 }
61 await this.yarn.exec([...add, `${name}@${tag}`], yarnOpts);
62 plugin = await Config.load({ devPlugins: false, userPlugins: false, root: path.join(this.config.dataDir, 'node_modules', name), name });
63 if (!plugin.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) {
64 throw new Error('plugin is invalid');
65 }
66 await this.refresh(plugin.root);
67 await this.add({ name, tag: range || tag, type: 'user' });
68 }
69 return plugin;
70 }
71 catch (error) {
72 await this.uninstall(name).catch(error => this.debug(error));
73 throw error;
74 }
75 }
76 // if yarn.lock exists, fetch locked dependencies
77 async refresh(root, { prod = true } = {}) {
78 if (fs.existsSync(path.join(root, 'yarn.lock'))) {
79 // use yarn.lock to fetch dependencies
80 await this.yarn.exec(prod ? ['--prod'] : [], { cwd: root, verbose: this.verbose });
81 }
82 }
83 async link(p) {
84 const c = await Config.load(path.resolve(p));
85 cli_ux_1.default.action.start(`${this.config.name}: linking plugin ${c.name}`);
86 if (!c.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) {
87 throw new errors_1.CLIError('plugin is not a valid oclif plugin');
88 }
89 await this.refresh(c.root, { prod: false });
90 await this.add({ type: 'link', name: c.name, root: c.root });
91 }
92 async add(plugin) {
93 const pjson = await this.pjson();
94 pjson.oclif.plugins = util_1.uniq([...pjson.oclif.plugins || [], plugin]);
95 await this.savePJSON(pjson);
96 }
97 async remove(name) {
98 const pjson = await this.pjson();
99 if (pjson.dependencies)
100 delete pjson.dependencies[name];
101 pjson.oclif.plugins = this.normalizePlugins(pjson.oclif.plugins)
102 .filter(p => p.name !== name);
103 await this.savePJSON(pjson);
104 }
105 async uninstall(name) {
106 try {
107 const pjson = await this.pjson();
108 if ((pjson.oclif.plugins || []).find(p => typeof p === 'object' && p.type === 'user' && p.name === name)) {
109 await this.yarn.exec(['remove', name], { cwd: this.config.dataDir, verbose: this.verbose });
110 }
111 }
112 catch (error) {
113 cli_ux_1.default.warn(error);
114 }
115 finally {
116 await this.remove(name);
117 }
118 }
119 // In this case we want these operations to happen
120 // sequentially so the `no-await-in-loop` rule is ugnored
121 /* eslint-disable no-await-in-loop */
122 async update() {
123 let plugins = (await this.list()).filter((p) => p.type === 'user');
124 if (plugins.length === 0)
125 return;
126 cli_ux_1.default.action.start(`${this.config.name}: Updating plugins`);
127 // migrate deprecated plugins
128 const aliases = this.config.pjson.oclif.aliases || {};
129 for (const [name, to] of Object.entries(aliases)) {
130 const plugin = plugins.find(p => p.name === name);
131 if (!plugin)
132 continue;
133 if (to)
134 await this.install(to);
135 await this.uninstall(name);
136 plugins = plugins.filter(p => p.name !== name);
137 }
138 if (plugins.find(p => Boolean(p.url))) {
139 await this.yarn.exec(['upgrade'], { cwd: this.config.dataDir, verbose: this.verbose });
140 }
141 const npmPlugins = plugins.filter(p => !p.url);
142 if (npmPlugins.length > 0) {
143 await this.yarn.exec(['add', ...npmPlugins.map(p => `${p.name}@${p.tag}`)], { cwd: this.config.dataDir, verbose: this.verbose });
144 }
145 for (const p of plugins) {
146 await this.refresh(path.join(this.config.dataDir, 'node_modules', p.name));
147 }
148 cli_ux_1.default.action.stop();
149 }
150 /* eslint-enable no-await-in-loop */
151 async hasPlugin(name) {
152 const list = await this.list();
153 return list.find(p => {
154 if (this.friendlyName(p.name) === this.friendlyName(name))
155 return true;
156 if (p.type === 'link') {
157 if (path.resolve(p.root) === path.resolve(name))
158 return true;
159 }
160 return false;
161 });
162 }
163 async yarnNodeVersion() {
164 try {
165 const f = await load_json_file_1.default(path.join(this.config.dataDir, 'node_modules', '.yarn-integrity'));
166 return f.nodeVersion;
167 }
168 catch (error) {
169 if (error.code !== 'ENOENT')
170 cli_ux_1.default.warn(error);
171 }
172 }
173 unfriendlyName(name) {
174 if (name.includes('@'))
175 return;
176 const scope = this.config.pjson.oclif.scope;
177 if (!scope)
178 return;
179 return `@${scope}/plugin-${name}`;
180 }
181 async maybeUnfriendlyName(name) {
182 const unfriendly = this.unfriendlyName(name);
183 if (unfriendly && await this.npmHasPackage(unfriendly)) {
184 return unfriendly;
185 }
186 return name;
187 }
188 friendlyName(name) {
189 const scope = this.config.pjson.oclif.scope;
190 if (!scope)
191 return name;
192 const match = name.match(`@${scope}/plugin-(.+)`);
193 if (!match)
194 return name;
195 return match[1];
196 }
197 // private async loadPlugin(plugin: Config.PJSON.PluginTypes) {
198 // return Config.load({...plugin as any, root: this.config.dataDir})
199 // }
200 async createPJSON() {
201 if (!fs.existsSync(this.pjsonPath)) {
202 await this.savePJSON(initPJSON);
203 }
204 }
205 get pjsonPath() {
206 return path.join(this.config.dataDir, 'package.json');
207 }
208 get npmRegistry() {
209 return this.config.npmRegistry || 'https://registry.npmjs.org';
210 }
211 async npmHasPackage(name) {
212 try {
213 const http = require('http-call').HTTP;
214 const url = `${this.npmRegistry}/${name.replace('/', '%2f')}`;
215 await http.get(url);
216 return true;
217 }
218 catch (error) {
219 this.debug(error);
220 if (error.statusCode === 404) {
221 return false;
222 }
223 throw error;
224 }
225 }
226 async savePJSON(pjson) {
227 pjson.oclif.plugins = this.normalizePlugins(pjson.oclif.plugins);
228 const fs = require('fs-extra');
229 await fs.outputJSON(this.pjsonPath, pjson, { spaces: 2 });
230 }
231 normalizePlugins(input) {
232 let plugins = (input || []).map(p => {
233 if (typeof p === 'string') {
234 return { name: p, type: 'user', tag: 'latest' };
235 }
236 return p;
237 });
238 plugins = util_1.uniqWith(plugins, (a, b) => a.name === b.name || (a.type === 'link' && b.type === 'link' && a.root === b.root));
239 return plugins;
240 }
241}
242exports.default = Plugins;