1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const core_1 = require("@oclif/core");
|
4 | const cli_ux_1 = require("cli-ux");
|
5 | const fs = require("fs");
|
6 | const load_json_file_1 = require("load-json-file");
|
7 | const path = require("path");
|
8 | const semver = require("semver");
|
9 | const child_process_1 = require("child_process");
|
10 | const util_1 = require("./util");
|
11 | const yarn_1 = require("./yarn");
|
12 | const initPJSON = { private: true, oclif: { schema: 1, plugins: [] }, dependencies: {} };
|
13 | class 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 |
|
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 |
|
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 |
|
89 | async refresh(root, { prod = true } = {}) {
|
90 | if (fs.existsSync(path.join(root, 'yarn.lock'))) {
|
91 |
|
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 |
|
132 |
|
133 |
|
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 |
|
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 |
|
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 |
|
208 |
|
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 | }
|
257 | exports.default = Plugins;
|