1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const Config = require("@oclif/config");
|
4 | const errors_1 = require("@oclif/errors");
|
5 | const cli_ux_1 = require("cli-ux");
|
6 | const fs = require("fs");
|
7 | const load_json_file_1 = require("load-json-file");
|
8 | const path = require("path");
|
9 | const semver = require("semver");
|
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 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 |
|
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 |
|
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 |
|
77 | async refresh(root, { prod = true } = {}) {
|
78 | if (fs.existsSync(path.join(root, 'yarn.lock'))) {
|
79 |
|
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 |
|
120 |
|
121 |
|
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 |
|
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 |
|
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 |
|
198 |
|
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 | }
|
242 | exports.default = Plugins;
|