UNPKG

15.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const errors_1 = require("@oclif/errors");
4const os = require("os");
5const path = require("path");
6const url_1 = require("url");
7const util_1 = require("util");
8const debug_1 = require("./debug");
9const Plugin = require("./plugin");
10const ts_node_1 = require("./ts-node");
11const util_2 = require("./util");
12// eslint-disable-next-line new-cap
13const debug = debug_1.default();
14const _pjson = require('../package.json');
15function channelFromVersion(version) {
16 const m = version.match(/[^-]+(?:-([^.]+))?/);
17 return (m && m[1]) || 'stable';
18}
19function hasManifest(p) {
20 try {
21 require(p);
22 return true;
23 }
24 catch (_a) {
25 return false;
26 }
27}
28const WSL = require('is-wsl');
29class Config {
30 // eslint-disable-next-line no-useless-constructor
31 constructor(options) {
32 this.options = options;
33 this._base = `${_pjson.name}@${_pjson.version}`;
34 this.debug = 0;
35 this.plugins = [];
36 this.warned = false;
37 }
38 // eslint-disable-next-line complexity
39 async load() {
40 const plugin = new Plugin.Plugin({ root: this.options.root });
41 await plugin.load();
42 this.plugins.push(plugin);
43 this.root = plugin.root;
44 this.pjson = plugin.pjson;
45 this.name = this.pjson.name;
46 this.version = this.options.version || this.pjson.version || '0.0.0';
47 this.channel = this.options.channel || channelFromVersion(this.version);
48 this.valid = plugin.valid;
49 this.arch = (os.arch() === 'ia32' ? 'x86' : os.arch());
50 this.platform = WSL ? 'wsl' : os.platform();
51 this.windows = this.platform === 'win32';
52 this.bin = this.pjson.oclif.bin || this.name;
53 this.dirname = this.pjson.oclif.dirname || this.name;
54 if (this.platform === 'win32')
55 this.dirname = this.dirname.replace('/', '\\');
56 this.userAgent = `${this.name}/${this.version} ${this.platform}-${this.arch} node-${process.version}`;
57 this.shell = this._shell();
58 this.debug = this._debug();
59 this.home = process.env.HOME || (this.windows && this.windowsHome()) || os.homedir() || os.tmpdir();
60 this.cacheDir = this.scopedEnvVar('CACHE_DIR') || this.macosCacheDir() || this.dir('cache');
61 this.configDir = this.scopedEnvVar('CONFIG_DIR') || this.dir('config');
62 this.dataDir = this.scopedEnvVar('DATA_DIR') || this.dir('data');
63 this.errlog = path.join(this.cacheDir, 'error.log');
64 this.binPath = this.scopedEnvVar('BINPATH');
65 this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.oclif.npmRegistry;
66 this.pjson.oclif.update = this.pjson.oclif.update || {};
67 this.pjson.oclif.update.node = this.pjson.oclif.update.node || {};
68 const s3 = this.pjson.oclif.update.s3 || {};
69 this.pjson.oclif.update.s3 = s3;
70 s3.bucket = this.scopedEnvVar('S3_BUCKET') || s3.bucket;
71 if (s3.bucket && !s3.host)
72 s3.host = `https://${s3.bucket}.s3.amazonaws.com`;
73 s3.templates = Object.assign(Object.assign({}, s3.templates), { target: Object.assign({ baseDir: '<%- bin %>', unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-<%- platform %>-<%- arch %><%- ext %>", versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>", manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- platform %>-<%- arch %>" }, s3.templates && s3.templates.target), vanilla: Object.assign({ unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %><%- ext %>", versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %><%- ext %>", baseDir: '<%- bin %>', manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %>version" }, s3.templates && s3.templates.vanilla) });
74 await this.loadUserPlugins();
75 await this.loadDevPlugins();
76 await this.loadCorePlugins();
77 debug('config done');
78 }
79 async loadCorePlugins() {
80 if (this.pjson.oclif.plugins) {
81 await this.loadPlugins(this.root, 'core', this.pjson.oclif.plugins);
82 }
83 }
84 async loadDevPlugins() {
85 if (this.options.devPlugins !== false) {
86 // do not load oclif.devPlugins in production
87 if (hasManifest(path.join(this.root, 'oclif.manifest.json')))
88 return;
89 try {
90 const devPlugins = this.pjson.oclif.devPlugins;
91 if (devPlugins)
92 await this.loadPlugins(this.root, 'dev', devPlugins);
93 }
94 catch (error) {
95 process.emitWarning(error);
96 }
97 }
98 }
99 async loadUserPlugins() {
100 if (this.options.userPlugins !== false) {
101 try {
102 const userPJSONPath = path.join(this.dataDir, 'package.json');
103 debug('reading user plugins pjson %s', userPJSONPath);
104 const pjson = await util_2.loadJSON(userPJSONPath);
105 this.userPJSON = pjson;
106 if (!pjson.oclif)
107 pjson.oclif = { schema: 1 };
108 if (!pjson.oclif.plugins)
109 pjson.oclif.plugins = [];
110 await this.loadPlugins(userPJSONPath, 'user', pjson.oclif.plugins.filter((p) => p.type === 'user'));
111 await this.loadPlugins(userPJSONPath, 'link', pjson.oclif.plugins.filter((p) => p.type === 'link'));
112 }
113 catch (error) {
114 if (error.code !== 'ENOENT')
115 process.emitWarning(error);
116 }
117 }
118 }
119 async runHook(event, opts) {
120 debug('start %s hook', event);
121 const promises = this.plugins.map(p => {
122 const debug = require('debug')([this.bin, p.name, 'hooks', event].join(':'));
123 const context = {
124 config: this,
125 debug,
126 exit(code = 0) {
127 errors_1.exit(code);
128 },
129 log(message, ...args) {
130 process.stdout.write(util_1.format(message, ...args) + '\n');
131 },
132 error(message, options = {}) {
133 errors_1.error(message, options);
134 },
135 warn(message) {
136 errors_1.warn(message);
137 },
138 };
139 return Promise.all((p.hooks[event] || [])
140 .map(async (hook) => {
141 try {
142 const f = ts_node_1.tsPath(p.root, hook);
143 debug('start', f);
144 const search = (m) => {
145 if (typeof m === 'function')
146 return m;
147 if (m.default && typeof m.default === 'function')
148 return m.default;
149 return Object.values(m).find((m) => typeof m === 'function');
150 };
151 await search(require(f)).call(context, Object.assign(Object.assign({}, opts), { config: this }));
152 debug('done');
153 }
154 catch (error) {
155 if (error && error.oclif && error.oclif.exit !== undefined)
156 throw error;
157 this.warn(error, `runHook ${event}`);
158 }
159 }));
160 });
161 await Promise.all(promises);
162 debug('%s hook done', event);
163 }
164 async runCommand(id, argv = []) {
165 debug('runCommand %s %o', id, argv);
166 const c = this.findCommand(id);
167 if (!c) {
168 await this.runHook('command_not_found', { id });
169 throw new errors_1.CLIError(`command ${id} not found`);
170 }
171 const command = c.load();
172 await this.runHook('prerun', { Command: command, argv });
173 const result = await command.run(argv, this);
174 await this.runHook('postrun', { Command: command, result: result, argv });
175 }
176 scopedEnvVar(k) {
177 return process.env[this.scopedEnvVarKey(k)];
178 }
179 scopedEnvVarTrue(k) {
180 const v = process.env[this.scopedEnvVarKey(k)];
181 return v === '1' || v === 'true';
182 }
183 scopedEnvVarKey(k) {
184 return [this.bin, k]
185 // eslint-disable-next-line no-useless-escape
186 .map(p => p.replace(/@/g, '').replace(/[-\/]/g, '_'))
187 .join('_')
188 .toUpperCase();
189 }
190 findCommand(id, opts = {}) {
191 const command = this.commands.find(c => c.id === id || c.aliases.includes(id));
192 if (command)
193 return command;
194 if (opts.must)
195 errors_1.error(`command ${id} not found`);
196 }
197 findTopic(name, opts = {}) {
198 const topic = this.topics.find(t => t.name === name);
199 if (topic)
200 return topic;
201 if (opts.must)
202 throw new Error(`topic ${name} not found`);
203 }
204 get commands() {
205 return util_2.flatMap(this.plugins, p => p.commands);
206 }
207 get commandIDs() {
208 return util_2.uniq(this.commands.map(c => c.id));
209 }
210 get topics() {
211 const topics = [];
212 for (const plugin of this.plugins) {
213 for (const topic of util_2.compact(plugin.topics)) {
214 const existing = topics.find(t => t.name === topic.name);
215 if (existing) {
216 existing.description = topic.description || existing.description;
217 existing.hidden = existing.hidden || topic.hidden;
218 }
219 else
220 topics.push(topic);
221 }
222 }
223 // add missing topics
224 for (const c of this.commands.filter(c => !c.hidden)) {
225 const parts = c.id.split(':');
226 while (parts.length) {
227 const name = parts.join(':');
228 if (name && !topics.find(t => t.name === name)) {
229 topics.push({ name, description: c.description });
230 }
231 parts.pop();
232 }
233 }
234 return topics;
235 }
236 s3Key(type, ext, options = {}) {
237 if (typeof ext === 'object')
238 options = ext;
239 else if (ext)
240 options.ext = ext;
241 const _ = require('lodash');
242 return _.template(this.pjson.oclif.update.s3.templates[options.platform ? 'target' : 'vanilla'][type])(Object.assign(Object.assign({}, this), options));
243 }
244 s3Url(key) {
245 const host = this.pjson.oclif.update.s3.host;
246 if (!host)
247 throw new Error('no s3 host is set');
248 const url = new url_1.URL(host);
249 url.pathname = path.join(url.pathname, key);
250 return url.toString();
251 }
252 dir(category) {
253 const base = process.env[`XDG_${category.toUpperCase()}_HOME`] ||
254 (this.windows && process.env.LOCALAPPDATA) ||
255 path.join(this.home, category === 'data' ? '.local/share' : '.' + category);
256 return path.join(base, this.dirname);
257 }
258 windowsHome() {
259 return this.windowsHomedriveHome() || this.windowsUserprofileHome();
260 }
261 windowsHomedriveHome() {
262 return (process.env.HOMEDRIVE && process.env.HOMEPATH && path.join(process.env.HOMEDRIVE, process.env.HOMEPATH));
263 }
264 windowsUserprofileHome() {
265 return process.env.USERPROFILE;
266 }
267 macosCacheDir() {
268 return (this.platform === 'darwin' && path.join(this.home, 'Library', 'Caches', this.dirname)) || undefined;
269 }
270 _shell() {
271 let shellPath;
272 const { SHELL, COMSPEC } = process.env;
273 if (SHELL) {
274 shellPath = SHELL.split('/');
275 }
276 else if (this.windows && COMSPEC) {
277 shellPath = COMSPEC.split(/\\|\//);
278 }
279 else {
280 shellPath = ['unknown'];
281 }
282 return shellPath[shellPath.length - 1];
283 }
284 _debug() {
285 if (this.scopedEnvVarTrue('DEBUG'))
286 return 1;
287 try {
288 const { enabled } = require('debug')(this.bin);
289 if (enabled)
290 return 1;
291 }
292 catch (_a) { }
293 return 0;
294 }
295 async loadPlugins(root, type, plugins, parent) {
296 if (!plugins || plugins.length === 0)
297 return;
298 debug('loading plugins', plugins);
299 await Promise.all((plugins || []).map(async (plugin) => {
300 try {
301 const opts = { type, root };
302 if (typeof plugin === 'string') {
303 opts.name = plugin;
304 }
305 else {
306 opts.name = plugin.name || opts.name;
307 opts.tag = plugin.tag || opts.tag;
308 opts.root = plugin.root || opts.root;
309 }
310 const instance = new Plugin.Plugin(opts);
311 await instance.load();
312 if (this.plugins.find(p => p.name === instance.name))
313 return;
314 this.plugins.push(instance);
315 if (parent) {
316 // eslint-disable-next-line require-atomic-updates
317 instance.parent = parent;
318 if (!parent.children)
319 parent.children = [];
320 parent.children.push(instance);
321 }
322 await this.loadPlugins(instance.root, type, instance.pjson.oclif.plugins || [], instance);
323 }
324 catch (error) {
325 this.warn(error, 'loadPlugins');
326 }
327 }));
328 }
329 warn(err, scope) {
330 if (this.warned)
331 return;
332 if (typeof err === 'string') {
333 process.emitWarning(err);
334 return;
335 }
336 if (err instanceof Error) {
337 const modifiedErr = err;
338 modifiedErr.name = `${err.name} Plugin: ${this.name}`;
339 modifiedErr.detail = util_2.compact([
340 err.detail,
341 `module: ${this._base}`,
342 scope && `task: ${scope}`,
343 `plugin: ${this.name}`,
344 `root: ${this.root}`,
345 'See more details with DEBUG=*',
346 ]).join('\n');
347 process.emitWarning(err);
348 return;
349 }
350 // err is an object
351 process.emitWarning('Config.warn expected either a string or Error, but instead received an object');
352 err.name = `${err.name} Plugin: ${this.name}`;
353 err.detail = util_2.compact([
354 err.detail,
355 `module: ${this._base}`,
356 scope && `task: ${scope}`,
357 `plugin: ${this.name}`,
358 `root: ${this.root}`,
359 'See more details with DEBUG=*',
360 ]).join('\n');
361 process.emitWarning(JSON.stringify(err));
362 }
363}
364exports.Config = Config;
365function isConfig(o) {
366 return o && Boolean(o._base);
367}
368async function load(opts = (module.parent && module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) {
369 if (typeof opts === 'string')
370 opts = { root: opts };
371 if (isConfig(opts))
372 return opts;
373 const config = new Config(opts);
374 await config.load();
375 return config;
376}
377exports.load = load;