UNPKG

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