1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const errors_1 = require("@oclif/errors");
|
4 | const os = require("os");
|
5 | const path = require("path");
|
6 | const url_1 = require("url");
|
7 | const util_1 = require("util");
|
8 | const debug_1 = require("./debug");
|
9 | const Plugin = require("./plugin");
|
10 | const ts_node_1 = require("./ts-node");
|
11 | const util_2 = require("./util");
|
12 |
|
13 | const debug = debug_1.default();
|
14 | const _pjson = require('../package.json');
|
15 | function channelFromVersion(version) {
|
16 | const m = version.match(/[^-]+(?:-([^.]+))?/);
|
17 | return (m && m[1]) || 'stable';
|
18 | }
|
19 | function hasManifest(p) {
|
20 | try {
|
21 | require(p);
|
22 | return true;
|
23 | }
|
24 | catch (_a) {
|
25 | return false;
|
26 | }
|
27 | }
|
28 | const WSL = require('is-wsl');
|
29 | class Config {
|
30 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | }
|
364 | exports.Config = Config;
|
365 | function isConfig(o) {
|
366 | return o && Boolean(o._base);
|
367 | }
|
368 | async 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 | }
|
377 | exports.load = load;
|